mirror of
https://github.com/TuxCoding/FastLogin.git
synced 2025-12-24 07:38:04 +01:00
Compare commits
8 Commits
dependabot
...
2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad815777d4 | ||
|
|
9a400f5ae1 | ||
|
|
c7254034bb | ||
|
|
7a8e5a59d0 | ||
|
|
d04e421927 | ||
|
|
6005f0db44 | ||
|
|
f50004f50d | ||
|
|
b6b95d50e5 |
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
@@ -25,8 +25,3 @@ updates:
|
||||
# Plugin require special evaluation about their compatibility
|
||||
- "com.lenis0012.bukkit:loginsecurity"
|
||||
- "com.comphenix.protocol:ProtocolLib"
|
||||
|
||||
ignore:
|
||||
# HikariCP dropped Java 8 support with 5.0
|
||||
- dependency-name: "com.zaxxer:HikariCP"
|
||||
update-types: ["version-update:semver-major"]
|
||||
|
||||
25
.github/release.yml
vendored
25
.github/release.yml
vendored
@@ -1,25 +0,0 @@
|
||||
# Configure how the release notes are generated for GitHub releases
|
||||
|
||||
changelog:
|
||||
# List of authors (like bots) and labels to exclude from pull requests
|
||||
exclude:
|
||||
labels:
|
||||
- ignore-for-release
|
||||
categories:
|
||||
- title: 🛠 Breaking Changes
|
||||
labels:
|
||||
- Semver-Major
|
||||
- breaking-change
|
||||
- title: 🎉 Exciting New Features
|
||||
labels:
|
||||
- Semver-Minor
|
||||
- enhancement
|
||||
- title: 🐞 Bugfixes
|
||||
labels:
|
||||
- bug
|
||||
- title: 👒 Dependencies
|
||||
labels:
|
||||
- dependencies
|
||||
- title: Other Changes
|
||||
labels:
|
||||
- "*"
|
||||
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
|
||||
permissions:
|
||||
# Only allow write for security, then all others default to read only
|
||||
# Only allow 'write' permission for security, then all others default to read only
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
@@ -59,6 +59,6 @@ jobs:
|
||||
run: mvn package -f "pom.xml" --batch-mode -V -e -Dfindbugs.skip -Dcheckstyle.skip -Dpmd.skip=true -Dspotbugs.skip -Denforcer.skip -Dmaven.javadoc.skip -DskipTests -Dmaven.test.skip.exec -Dlicense.skip=true -Drat.skip=true -Dspotless.check.skip=true -t /home/runner/.m2/toolchains.xml
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
2
.github/workflows/maven.yml
vendored
2
.github/workflows/maven.yml
vendored
@@ -64,4 +64,4 @@ jobs:
|
||||
|
||||
- name: Submit Dependency Snapshot
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
uses: advanced-security/maven-dependency-submission-action@v5.0.0
|
||||
uses: advanced-security/maven-dependency-submission-action@v4.0.3
|
||||
|
||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,3 +1,29 @@
|
||||
# 2.0
|
||||
|
||||
## Major changes
|
||||
|
||||
* Bumped minimum Java version to 11 make use of modern Java performance features
|
||||
* Report back if really still need the old versions
|
||||
* Then we could make use of versioned code, but that requires more coding effort
|
||||
|
||||
## Added
|
||||
|
||||
* Support for HTTP/2 for contacting Mojang
|
||||
|
||||
## Changed
|
||||
|
||||
* Updated many dependencies
|
||||
|
||||
## Removed
|
||||
|
||||
Dropped some features listed below. Please contact us if you still need them
|
||||
|
||||
* Dropped Java support < 11
|
||||
* Removed configuration option to add multiple outgoing IPv4 towards Mojang
|
||||
* Dropped support for ProtocolSupport seems to unsupported
|
||||
|
||||
[...] A lot of changes
|
||||
|
||||
### 1.11
|
||||
|
||||
* TODO: Replace reflection with methodhandles
|
||||
|
||||
10
README.md
10
README.md
@@ -1,7 +1,5 @@
|
||||
# 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).
|
||||
|
||||
@@ -32,11 +30,7 @@ Development builds contain the latest changes from the Source-Code. They are ble
|
||||
but also include features, enhancements and bug fixes that are not yet in a released version. If you click on the left
|
||||
side on `Changes`, you can see iterative change sets leading to a specific build.
|
||||
|
||||
~~You can download them from here: [CodeMC(Jenkins)](https://ci.codemc.org/job/Games647/job/FastLogin/)~~
|
||||
|
||||
Currently broken due changed usernames. Download it from [here](https://github.com/TuxCoding/FastLogin/releases)
|
||||
|
||||
|
||||
You can download them from here: https://ci.codemc.org/job/Games647/job/FastLogin/
|
||||
|
||||
***
|
||||
|
||||
@@ -70,7 +64,7 @@ Possible values: `Premium`, `Cracked`, `Unknown`
|
||||
* Server software in offlinemode:
|
||||
* Spigot (or a fork e.g. Paper) 1.8.8+
|
||||
* Protocol plugin:
|
||||
* [ProtocolLib 5.3+ with development build above 720](https://www.spigotmc.org/resources/protocollib.1997/) or
|
||||
* [ProtocolLib 5.2+](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.6.0</version>
|
||||
<version>3.5.3</version>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<shadedArtifactAttached>false</shadedArtifactAttached>
|
||||
@@ -119,13 +119,16 @@
|
||||
<!-- PaperSpigot API, PaperLib, datafixupper and bungeecord-chat -->
|
||||
<repository>
|
||||
<id>papermc</id>
|
||||
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||
<url>https://papermc.io/repo/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 -->
|
||||
@@ -168,7 +171,7 @@
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>1.21.6-R0.1-SNAPSHOT</version>
|
||||
<version>1.20.6-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
<!-- Use our own newer api version -->
|
||||
<exclusions>
|
||||
@@ -208,7 +211,7 @@
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>5.3.0</version>
|
||||
<version>5.1.0</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@@ -279,7 +282,7 @@
|
||||
<dependency>
|
||||
<groupId>me.clip</groupId>
|
||||
<artifactId>placeholderapi</artifactId>
|
||||
<version>2.11.6</version>
|
||||
<version>2.11.5</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
@@ -294,7 +297,7 @@
|
||||
<dependency>
|
||||
<groupId>fr.xephi</groupId>
|
||||
<artifactId>authme</artifactId>
|
||||
<version>5.6.0</version>
|
||||
<version>5.6.0-beta2</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
@@ -348,22 +351,6 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.zigazajc007</groupId>
|
||||
<artifactId>Passky</artifactId>
|
||||
<version>v3.0.0</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
|
||||
<!-- Exclude dependencies to prevent potential version conflicts-->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!--No maven repository :(-->
|
||||
<dependency>
|
||||
<groupId>de.st_ddt.crazy</groupId>
|
||||
@@ -395,7 +382,7 @@
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk18on</artifactId>
|
||||
<version>1.80</version>
|
||||
<version>1.78.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.github.games647.craftapi.model.skin.SkinProperty;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
|
||||
import com.github.games647.fastlogin.bukkit.auth.protocollib.packet.ClientPublicKey;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -25,24 +25,21 @@
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.github.games647.fastlogin.bukkit.auth.AuthenticationBackend;
|
||||
import com.github.games647.fastlogin.bukkit.auth.ConnectionListener;
|
||||
import com.github.games647.fastlogin.bukkit.auth.protocollib.ProtocolAuthentication;
|
||||
import com.github.games647.fastlogin.bukkit.auth.proxy.ProxyAuthentication;
|
||||
import com.github.games647.fastlogin.bukkit.command.CrackedCommand;
|
||||
import com.github.games647.fastlogin.bukkit.command.PremiumCommand;
|
||||
import com.github.games647.fastlogin.bukkit.command.DeleteCommand;
|
||||
import com.github.games647.fastlogin.bukkit.listener.ConnectionListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.PaperCacheListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.ProtocolLibListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.SkinApplyListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocolsupport.ProtocolSupportListener;
|
||||
import com.github.games647.fastlogin.bukkit.task.DelayedAuthHook;
|
||||
import com.github.games647.fastlogin.bukkit.hook.DelayedAuthHook;
|
||||
import com.github.games647.fastlogin.core.CommonUtil;
|
||||
import com.github.games647.fastlogin.core.PremiumStatus;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService;
|
||||
import com.github.games647.fastlogin.core.hooks.bedrock.BedrockService;
|
||||
import com.github.games647.fastlogin.core.hooks.bedrock.FloodgateService;
|
||||
import com.github.games647.fastlogin.core.hooks.bedrock.GeyserService;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.shared.PlatformPlugin;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -56,6 +53,8 @@ import org.slf4j.Logger;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
@@ -72,18 +71,30 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
|
||||
Duration.ofMinutes(1), -1
|
||||
);
|
||||
|
||||
@Getter
|
||||
private final Map<UUID, PremiumStatus> premiumPlayers = new ConcurrentHashMap<>();
|
||||
private final Logger logger;
|
||||
|
||||
private boolean serverStarted;
|
||||
private BungeeManager bungeeManager;
|
||||
private final BukkitScheduler scheduler;
|
||||
|
||||
@Getter
|
||||
private final Collection<UUID> pendingConfirms = new HashSet<>();
|
||||
|
||||
@Getter
|
||||
private FastLoginCore<Player, CommandSender, FastLoginBukkit> core;
|
||||
|
||||
@Getter
|
||||
private FloodgateService floodgateService;
|
||||
private GeyserService geyserService;
|
||||
|
||||
private PremiumPlaceholder premiumPlaceholder;
|
||||
|
||||
@Getter
|
||||
private AuthenticationBackend backend;
|
||||
|
||||
@Getter
|
||||
private boolean initialized;
|
||||
|
||||
public FastLoginBukkit() {
|
||||
this.logger = CommonUtil.initializeLoggerService(getLogger());
|
||||
this.scheduler = new BukkitScheduler(this, logger);
|
||||
@@ -105,58 +116,48 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
bungeeManager = new BungeeManager(this);
|
||||
bungeeManager.initialize();
|
||||
|
||||
PluginManager pluginManager = getServer().getPluginManager();
|
||||
if (bungeeManager.isEnabled()) {
|
||||
markInitialized();
|
||||
} else {
|
||||
if (!core.setupDatabase()) {
|
||||
setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
AntiBotService antiBotService = core.getAntiBotService();
|
||||
if (pluginManager.isPluginEnabled("ProtocolSupport")) {
|
||||
pluginManager.registerEvents(new ProtocolSupportListener(this, antiBotService), this);
|
||||
} else if (pluginManager.isPluginEnabled("ProtocolLib")) {
|
||||
ProtocolLibListener.register(this, antiBotService, core.getConfig().getBoolean("verifyClientKeys"));
|
||||
|
||||
//if server is using paper - we need to set the skin at pre login anyway, so no need for this listener
|
||||
if (!isPaper() && getConfig().getBoolean("forwardSkin")) {
|
||||
pluginManager.registerEvents(new SkinApplyListener(this), this);
|
||||
}
|
||||
} else {
|
||||
logger.warn("Either ProtocolLib or ProtocolSupport have to be installed if you don't use BungeeCord");
|
||||
setEnabled(false);
|
||||
return;
|
||||
}
|
||||
backend = initializeAuthenticationBackend();
|
||||
if (backend == null) {
|
||||
logger.warn("Either ProtocolLib or ProtocolSupport have to be installed if you don't use BungeeCord");
|
||||
setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
//delay dependency setup because we load the plugin very early where plugins are initialized yet
|
||||
getServer().getScheduler().runTaskLater(this, new DelayedAuthHook(this), 5L);
|
||||
backend.init(getServer().getPluginManager());
|
||||
PluginManager pluginManager = getServer().getPluginManager();
|
||||
|
||||
pluginManager.registerEvents(new ConnectionListener(this), this);
|
||||
|
||||
//if server is using paper - we need to add one more listener to correct the user cache usage
|
||||
if (isPaper()) {
|
||||
pluginManager.registerEvents(new PaperCacheListener(this), this);
|
||||
}
|
||||
|
||||
registerCommands();
|
||||
|
||||
if (pluginManager.isPluginEnabled("PlaceholderAPI")) {
|
||||
premiumPlaceholder = new PremiumPlaceholder(this);
|
||||
premiumPlaceholder.register();
|
||||
}
|
||||
|
||||
// delay dependency setup because we load the plugin very early where plugins are initialized yet
|
||||
getServer().getScheduler().runTaskLater(this, new DelayedAuthHook(this), 5L);
|
||||
}
|
||||
|
||||
private AuthenticationBackend initializeAuthenticationBackend() {
|
||||
AuthenticationBackend proxyVerifier = new ProxyAuthentication(this);
|
||||
if (proxyVerifier.isAvailable()) {
|
||||
return proxyVerifier;
|
||||
}
|
||||
|
||||
logger.warn("Disabling Minecraft proxy configuration. Assuming direct connections from now on.");
|
||||
AuthenticationBackend protocolAuthentication = new ProtocolAuthentication(this);
|
||||
if (protocolAuthentication.isAvailable()) {
|
||||
return protocolAuthentication;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void registerCommands() {
|
||||
//register commands using a unique name
|
||||
Optional.ofNullable(getCommand("premium")).ifPresent(c -> c.setExecutor(new PremiumCommand(this)));
|
||||
Optional.ofNullable(getCommand("cracked")).ifPresent(c -> c.setExecutor(new CrackedCommand(this)));
|
||||
Optional.ofNullable(getCommand("fldelete")).ifPresent(c -> c.setExecutor(new DeleteCommand(this)));
|
||||
}
|
||||
|
||||
private boolean initializeFloodgate() {
|
||||
@@ -184,25 +185,13 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
|
||||
core.close();
|
||||
}
|
||||
|
||||
if (bungeeManager != null) {
|
||||
bungeeManager.cleanup();
|
||||
if (backend != null) {
|
||||
backend.stop();
|
||||
}
|
||||
|
||||
if (premiumPlaceholder != null && getServer().getPluginManager().isPluginEnabled("PlaceholderAPI")) {
|
||||
try {
|
||||
premiumPlaceholder.unregister();
|
||||
} catch (Exception | NoSuchMethodError exception) {
|
||||
logger.error("Failed to unregister placeholder", exception);
|
||||
}
|
||||
premiumPlaceholder.unregister();
|
||||
}
|
||||
|
||||
if (getServer().getPluginManager().isPluginEnabled("ProtocolLib")) {
|
||||
ProtocolLibrary.getProtocolManager().getAsynchronousManager().unregisterAsyncHandlers(this);
|
||||
}
|
||||
}
|
||||
|
||||
public FastLoginCore<Player, CommandSender, FastLoginBukkit> getCore() {
|
||||
return core;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,10 +223,6 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
|
||||
loginSession.remove(id);
|
||||
}
|
||||
|
||||
public Map<UUID, PremiumStatus> getPremiumPlayers() {
|
||||
return premiumPlayers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the premium status of an online player.
|
||||
* {@snippet :
|
||||
@@ -265,24 +250,6 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
|
||||
return premiumPlayers.getOrDefault(onlinePlayer, PremiumStatus.UNKNOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait before the server is fully started. This is workaround, because connections right on startup are not
|
||||
* injected by ProtocolLib
|
||||
*
|
||||
* @return true if ProtocolLib can now intercept packets
|
||||
*/
|
||||
public boolean isServerFullyStarted() {
|
||||
return serverStarted;
|
||||
}
|
||||
|
||||
public void markInitialized() {
|
||||
this.serverStarted = true;
|
||||
}
|
||||
|
||||
public BungeeManager getBungeeManager() {
|
||||
return bungeeManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getPluginFolder() {
|
||||
return getDataFolder().toPath();
|
||||
@@ -315,12 +282,21 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
|
||||
return Bukkit.getServer().getPluginManager().getPlugin(name) != null;
|
||||
}
|
||||
|
||||
public FloodgateService getFloodgateService() {
|
||||
return floodgateService;
|
||||
public void setInitialized(boolean hookFound) {
|
||||
if (backend instanceof ProxyAuthentication) {
|
||||
logger.info("BungeeCord setting detected. No auth plugin is required");
|
||||
} else if (!hookFound) {
|
||||
logger.warn("No auth plugin were found by this plugin "
|
||||
+ "(other plugins could hook into this after the initialization of this plugin)"
|
||||
+ "and BungeeCord is deactivated. "
|
||||
+ "Either one or both of the checks have to pass in order to use this plugin");
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
public GeyserService getGeyserService() {
|
||||
return geyserService;
|
||||
public ProxyAuthentication getBungeeManager() {
|
||||
return (ProxyAuthentication) backend;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -328,19 +304,7 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
|
||||
if (floodgateService != null) {
|
||||
return floodgateService;
|
||||
}
|
||||
|
||||
return geyserService;
|
||||
}
|
||||
|
||||
private boolean isPaper() {
|
||||
return isClassAvailable("com.destroystokyo.paper.PaperConfig").isPresent()
|
||||
|| isClassAvailable("io.papermc.paper.configuration.Configuration").isPresent();
|
||||
}
|
||||
|
||||
private Optional<Class<?>> isClassAvailable(String clazzName) {
|
||||
try {
|
||||
return Optional.of(Class.forName(clazzName));
|
||||
} catch (ClassNotFoundException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,44 +23,15 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocolsupport;
|
||||
package com.github.games647.fastlogin.bukkit.auth;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.LoginSource;
|
||||
import protocolsupport.api.events.PlayerLoginStartEvent;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
public interface AuthenticationBackend {
|
||||
|
||||
public class ProtocolLoginSource implements LoginSource {
|
||||
boolean isAvailable();
|
||||
|
||||
private final PlayerLoginStartEvent loginStartEvent;
|
||||
void init(PluginManager pluginManager);
|
||||
|
||||
public ProtocolLoginSource(PlayerLoginStartEvent loginStartEvent) {
|
||||
this.loginStartEvent = loginStartEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableOnlinemode() {
|
||||
loginStartEvent.setOnlineMode(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kick(String message) {
|
||||
loginStartEvent.denyLogin(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getAddress() {
|
||||
return loginStartEvent.getConnection().getRawAddress();
|
||||
}
|
||||
|
||||
public PlayerLoginStartEvent getLoginStartEvent() {
|
||||
return loginStartEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + '{'
|
||||
+ "loginStartEvent=" + loginStartEvent
|
||||
+ '}';
|
||||
}
|
||||
void stop();
|
||||
}
|
||||
@@ -23,23 +23,17 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener;
|
||||
package com.github.games647.fastlogin.bukkit.auth;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.task.FloodgateAuthTask;
|
||||
import com.github.games647.fastlogin.bukkit.task.ForceLoginTask;
|
||||
import com.github.games647.fastlogin.core.hooks.bedrock.FloodgateService;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerLoginEvent;
|
||||
import org.bukkit.event.player.PlayerLoginEvent.Result;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.metadata.Metadatable;
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
|
||||
/**
|
||||
@@ -56,14 +50,6 @@ public class ConnectionListener implements Listener {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
|
||||
removeBlockedStatus(loginEvent.getPlayer());
|
||||
if (loginEvent.getResult() == Result.ALLOWED && !plugin.isServerFullyStarted()) {
|
||||
loginEvent.disallow(Result.KICK_OTHER, plugin.getCore().getMessage("not-started"));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlayerJoin(PlayerJoinEvent joinEvent) {
|
||||
Player player = joinEvent.getPlayer();
|
||||
@@ -90,7 +76,6 @@ 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;
|
||||
}
|
||||
}
|
||||
@@ -103,21 +88,13 @@ public class ConnectionListener implements Listener {
|
||||
Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player, session);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, forceLoginTask);
|
||||
}
|
||||
|
||||
plugin.getBungeeManager().markJoinEventFired(player);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent quitEvent) {
|
||||
Player player = quitEvent.getPlayer();
|
||||
|
||||
removeBlockedStatus(player);
|
||||
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
|
||||
plugin.getPendingConfirms().remove(player.getUniqueId());
|
||||
plugin.getPremiumPlayers().remove(player.getUniqueId());
|
||||
plugin.getBungeeManager().cleanup(player);
|
||||
}
|
||||
|
||||
private void removeBlockedStatus(Metadatable player) {
|
||||
player.removeMetadata(plugin.getName(), plugin);
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.task;
|
||||
package com.github.games647.fastlogin.bukkit.auth;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
@@ -23,7 +23,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.task;
|
||||
package com.github.games647.fastlogin.bukkit.auth;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
@@ -80,7 +80,7 @@ public class ForceLoginTask extends ForceLoginManagement<Player, CommandSender,
|
||||
|
||||
@Override
|
||||
public void onForceActionSuccess(LoginSession session) {
|
||||
if (core.getPlugin().getBungeeManager().isEnabled()) {
|
||||
if (core.getPlugin().getBungeeManager().isAvailable()) {
|
||||
core.getPlugin().getBungeeManager().sendPluginMessage(player, new SuccessMessage());
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
package com.github.games647.fastlogin.bukkit.auth;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2024 games647 and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.auth;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.auth.protocollib.SkinApplyListener;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class LocalAuthentication implements AuthenticationBackend {
|
||||
|
||||
protected final FastLoginBukkit plugin;
|
||||
|
||||
protected LocalAuthentication(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(PluginManager pluginManager) {
|
||||
if (!plugin.getCore().setupDatabase()) {
|
||||
plugin.setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// if server is using paper - we need to add one more listener to correct the user cache usage
|
||||
if (isPaper()) {
|
||||
pluginManager.registerEvents(new PaperCacheListener(plugin), plugin);
|
||||
} else if (plugin.getConfig().getBoolean("forwardSkin")) {
|
||||
//if server is using paper - we need to set the skin at pre login anyway, so no need for this listener
|
||||
pluginManager.registerEvents(new SkinApplyListener(plugin), plugin);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPaper() {
|
||||
return isClassAvailable("com.destroystokyo.paper.PaperConfig").isPresent()
|
||||
|| isClassAvailable("io.papermc.paper.configuration.Configuration").isPresent();
|
||||
}
|
||||
|
||||
private Optional<Class<?>> isClassAvailable(String clazzName) {
|
||||
try {
|
||||
return Optional.of(Class.forName(clazzName));
|
||||
} catch (ClassNotFoundException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener;
|
||||
package com.github.games647.fastlogin.bukkit.auth;
|
||||
|
||||
import com.destroystokyo.paper.profile.ProfileProperty;
|
||||
import com.github.games647.craftapi.model.skin.Textures;
|
||||
@@ -23,14 +23,15 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
package com.github.games647.fastlogin.bukkit.auth.protocollib;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
|
||||
import com.github.games647.fastlogin.bukkit.auth.protocollib.packet.ClientPublicKey;
|
||||
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;
|
||||
@@ -40,7 +41,6 @@ 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,7 +53,6 @@ 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;
|
||||
@@ -199,9 +198,9 @@ final class EncryptionUtil {
|
||||
|
||||
private static PublicKey loadMojangSessionKey()
|
||||
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
URL keyUrl = FastLoginBukkit.class.getClassLoader().getResource("yggdrasil_session_pubkey.der");
|
||||
byte[] keyData = Resources.toByteArray(keyUrl);
|
||||
KeySpec keySpec = new X509EncodedKeySpec(keyData);
|
||||
val keyUrl = FastLoginBukkit.class.getClassLoader().getResource("yggdrasil_session_pubkey.der");
|
||||
val keyData = Resources.toByteArray(keyUrl);
|
||||
val keySpec = new X509EncodedKeySpec(keyData);
|
||||
|
||||
return KeyFactory.getInstance("RSA").generatePublic(keySpec);
|
||||
}
|
||||
@@ -23,14 +23,14 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
package com.github.games647.fastlogin.bukkit.auth.protocollib;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.auth.protocollib.packet.ClientPublicKey;
|
||||
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
|
||||
import com.github.games647.fastlogin.core.shared.JoinManagement;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2024 games647 and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.auth.protocollib;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.auth.LocalAuthentication;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerLoginEvent;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
public class ProtocolAuthentication extends LocalAuthentication implements Listener {
|
||||
|
||||
public ProtocolAuthentication(FastLoginBukkit plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return plugin.getServer().getPluginManager().isPluginEnabled("ProtocolLib");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(PluginManager pluginManager) {
|
||||
pluginManager.registerEvents(this, plugin);
|
||||
|
||||
FastLoginCore<Player, CommandSender, FastLoginBukkit> core = plugin.getCore();
|
||||
AntiBotService antiBotService = core.getAntiBotService();
|
||||
ProtocolLibListener.register(plugin, antiBotService, core.getConfig().getBoolean("verifyClientKeys"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
ProtocolLibrary.getProtocolManager().getAsynchronousManager().unregisterAsyncHandlers(plugin);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
|
||||
if (loginEvent.getResult() == PlayerLoginEvent.Result.ALLOWED && !plugin.isInitialized()) {
|
||||
loginEvent.disallow(PlayerLoginEvent.Result.KICK_OTHER, plugin.getCore().getMessage("not-started"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,32 +23,33 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
package com.github.games647.fastlogin.bukkit.auth.protocollib;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
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.netty.channel.NettyChannelInjector;
|
||||
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
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;
|
||||
import com.github.games647.fastlogin.bukkit.auth.protocollib.packet.ClientPublicKey;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService.Action;
|
||||
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;
|
||||
@@ -74,6 +75,7 @@ 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();
|
||||
@@ -92,6 +94,7 @@ 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) {
|
||||
@@ -107,20 +110,20 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||
if (packetEvent.isCancelled()
|
||||
|| plugin.getCore().getAuthPluginHook() == null
|
||||
|| !plugin.isServerFullyStarted()) {
|
||||
|| !plugin.isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player sender = packetEvent.getPlayer();
|
||||
PacketType packetType = getOverriddenType(packetEvent.getPacketType());
|
||||
|
||||
plugin.getLog().info("New incoming packet {} from {}", packetType, sender.getName());
|
||||
plugin.getLog().info("New packet {} from {}", packetType, sender);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -245,9 +248,8 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
// public key is sent separate
|
||||
clientKey = Optional.empty();
|
||||
} else {
|
||||
Optional<Optional<WrappedProfileKeyData>> profileKey = packet.getOptionals(
|
||||
BukkitConverters.getWrappedPublicKeyDataConverter()
|
||||
).optionRead(0);
|
||||
val profileKey = packet.getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter())
|
||||
.optionRead(0);
|
||||
|
||||
clientKey = profileKey.flatMap(Function.identity()).flatMap(data -> {
|
||||
Instant expires = data.getExpireTime();
|
||||
@@ -295,17 +297,16 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
return profile.getName();
|
||||
}
|
||||
|
||||
private FloodgatePlayer getFloodgatePlayer(Player player) {
|
||||
Channel channel = getChannel(player);
|
||||
AttributeKey<FloodgatePlayer> floodgateAttribute = AttributeKey.valueOf("floodgate-player");
|
||||
return channel.attr(floodgateAttribute).get();
|
||||
private static PlayerInjectionHandler getHandler() {
|
||||
PacketFilterManager manager = (PacketFilterManager) ProtocolLibrary.getProtocolManager();
|
||||
FieldAccessor accessor = Accessors.getFieldAccessor(manager.getClass(), PlayerInjectionHandler.class, true);
|
||||
return (PlayerInjectionHandler) accessor.get(manager);
|
||||
}
|
||||
|
||||
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);
|
||||
private FloodgatePlayer getFloodgatePlayer(Player player) {
|
||||
Channel channel = handler.getChannel(player);
|
||||
AttributeKey<FloodgatePlayer> floodgateAttribute = AttributeKey.valueOf("floodgate-player");
|
||||
return channel.attr(floodgateAttribute).get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -324,7 +325,7 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
}
|
||||
|
||||
// kick the player, if necessary
|
||||
Channel channel = getChannel(packetEvent.getPlayer());
|
||||
Channel channel = handler.getChannel(packetEvent.getPlayer());
|
||||
AttributeKey<String> kickMessageAttribute = AttributeKey.valueOf("floodgate-kick-message");
|
||||
String kickMessage = channel.attr(kickMessageAttribute).get();
|
||||
if (kickMessage != null) {
|
||||
@@ -23,14 +23,14 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
package com.github.games647.fastlogin.bukkit.auth.protocollib;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
|
||||
import com.github.games647.fastlogin.bukkit.auth.protocollib.packet.ClientPublicKey;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSource;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
package com.github.games647.fastlogin.bukkit.auth.protocollib;
|
||||
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
|
||||
@@ -23,12 +23,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
package com.github.games647.fastlogin.bukkit.auth.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;
|
||||
@@ -48,8 +47,9 @@ import com.github.games647.craftapi.model.skin.SkinProperty;
|
||||
import com.github.games647.craftapi.resolver.MojangResolver;
|
||||
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 com.github.games647.fastlogin.bukkit.auth.InetUtils;
|
||||
import com.github.games647.fastlogin.bukkit.auth.protocollib.packet.ClientPublicKey;
|
||||
import lombok.val;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
@@ -218,14 +218,15 @@ public class VerifyResponseTask implements Runnable {
|
||||
|
||||
//try to get the networkManager from ProtocolLib
|
||||
private Object getNetworkManager() throws ClassNotFoundException {
|
||||
NettyChannelInjector injectorContainer = (NettyChannelInjector) Accessors.getMethodAccessorOrNull(
|
||||
TemporaryPlayerFactory.class, "getInjectorFromPlayer", Player.class
|
||||
).invoke(null, player);
|
||||
Object injectorContainer = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
||||
|
||||
FieldAccessor accessor = Accessors.getFieldAccessorOrNull(
|
||||
NettyChannelInjector.class, "networkManager", Object.class
|
||||
);
|
||||
return accessor.get(injectorContainer);
|
||||
// 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);
|
||||
}
|
||||
|
||||
private boolean enableEncryption(SecretKey loginKey) throws IllegalArgumentException {
|
||||
@@ -306,7 +307,7 @@ public class VerifyResponseTask implements Runnable {
|
||||
startPacket.getStrings().write(0, username);
|
||||
|
||||
EquivalentConverter<WrappedProfileKeyData> converter = BukkitConverters.getWrappedPublicKeyDataConverter();
|
||||
Optional<WrappedProfileKeyData> wrappedKey = Optional.ofNullable(clientKey).map(key ->
|
||||
val wrappedKey = Optional.ofNullable(clientKey).map(key ->
|
||||
new WrappedProfileKeyData(clientKey.expiry(), clientKey.key(), clientKey.signature())
|
||||
);
|
||||
|
||||
@@ -23,40 +23,22 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib.packet;
|
||||
package com.github.games647.fastlogin.bukkit.auth.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 {
|
||||
|
||||
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);
|
||||
}
|
||||
Instant expiry;
|
||||
PublicKey key;
|
||||
byte[] signature;
|
||||
|
||||
public boolean isExpired(Instant verifyTimestamp) {
|
||||
return !verifyTimestamp.isBefore(expiry);
|
||||
@@ -23,124 +23,77 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
package com.github.games647.fastlogin.bukkit.auth.proxy;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.listener.BungeeListener;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.auth.AuthenticationBackend;
|
||||
import com.github.games647.fastlogin.core.message.ChannelMessage;
|
||||
import com.github.games647.fastlogin.core.message.LoginActionMessage;
|
||||
import com.github.games647.fastlogin.core.message.NamespaceKey;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import io.papermc.paper.configuration.ServerConfiguration;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.messaging.PluginMessageRecipient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.github.games647.fastlogin.core.message.ChangePremiumMessage.CHANGE_CHANNEL;
|
||||
import static com.github.games647.fastlogin.core.message.SuccessMessage.SUCCESS_CHANNEL;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
|
||||
public class BungeeManager {
|
||||
|
||||
private static final String LEGACY_FILE_NAME = "proxy-whitelist.txt";
|
||||
private static final String FILE_NAME = "allowed-proxies.txt";
|
||||
|
||||
//null if proxies allowed list is empty so bungeecord support is disabled
|
||||
private Set<UUID> proxyIds;
|
||||
public class ProxyAuthentication implements AuthenticationBackend {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
private boolean enabled;
|
||||
private ProxyVerifier verifier;
|
||||
|
||||
private final Collection<UUID> firedJoinEvents = new HashSet<>();
|
||||
|
||||
public BungeeManager(FastLoginBukkit plugin) {
|
||||
public ProxyAuthentication(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
//remove old blocked status
|
||||
Bukkit.getOnlinePlayers().forEach(player -> player.removeMetadata(plugin.getName(), plugin));
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return detectProxy();
|
||||
}
|
||||
|
||||
public void sendPluginMessage(PluginMessageRecipient player, ChannelMessage message) {
|
||||
if (player != null) {
|
||||
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
|
||||
message.writeTo(dataOutput);
|
||||
@Override
|
||||
public void init(PluginManager pluginManager) {
|
||||
verifier = new ProxyVerifier(plugin);
|
||||
verifier.loadSecrets();
|
||||
|
||||
NamespaceKey channel = new NamespaceKey(plugin.getName(), message.getChannelName());
|
||||
player.sendPluginMessage(plugin, channel.getCombinedName(), dataOutput.toByteArray());
|
||||
registerPluginChannels();
|
||||
|
||||
pluginManager.registerEvents(new ProxyConnectionListener(plugin, verifier), plugin);
|
||||
|
||||
plugin.getLog().info("Found enabled proxy configuration");
|
||||
plugin.getLog().info("Remember to follow the proxy guide to complete your setup");
|
||||
}
|
||||
|
||||
private void registerPluginChannels() {
|
||||
Server server = Bukkit.getServer();
|
||||
|
||||
// check for incoming messages from the bungeecord version of this plugin
|
||||
String groupId = plugin.getName();
|
||||
String forceChannel = NamespaceKey.getCombined(groupId, LoginActionMessage.FORCE_CHANNEL);
|
||||
server.getMessenger().registerIncomingPluginChannel(plugin, forceChannel, new ProxyListener(plugin, verifier));
|
||||
|
||||
// outgoing
|
||||
String successChannel = new NamespaceKey(groupId, SUCCESS_CHANNEL).getCombinedName();
|
||||
String changeChannel = new NamespaceKey(groupId, CHANGE_CHANNEL).getCombinedName();
|
||||
server.getMessenger().registerOutgoingPluginChannel(plugin, successChannel);
|
||||
server.getMessenger().registerOutgoingPluginChannel(plugin, changeChannel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (verifier != null) {
|
||||
verifier.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
enabled = detectProxy();
|
||||
|
||||
if (enabled) {
|
||||
proxyIds = loadBungeeCordIds();
|
||||
if (proxyIds.isEmpty()) {
|
||||
plugin.getLog().info("No valid IDs found. Minecraft proxy support cannot work in the current state");
|
||||
}
|
||||
|
||||
registerPluginChannels();
|
||||
plugin.getLog().info("Found enabled proxy configuration");
|
||||
plugin.getLog().info("Remember to follow the proxy guide to complete your setup");
|
||||
} else {
|
||||
plugin.getLog().warn("Disabling Minecraft proxy configuration. Assuming direct connections from now on.");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isProxySupported(String className, String fieldName)
|
||||
throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
|
||||
return Class.forName(className).getDeclaredField(fieldName).getBoolean(null);
|
||||
}
|
||||
|
||||
private boolean isVelocityEnabled()
|
||||
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException, ClassNotFoundException,
|
||||
NoSuchMethodException, InvocationTargetException {
|
||||
try {
|
||||
Class<?> globalConfig = Class.forName("io.papermc.paper.configuration.GlobalConfiguration");
|
||||
Object global = globalConfig.getDeclaredMethod("get").invoke(null);
|
||||
Object proxiesConfiguration = global.getClass().getDeclaredField("proxies").get(global);
|
||||
|
||||
Field velocitySectionField = proxiesConfiguration.getClass().getDeclaredField("velocity");
|
||||
Object velocityConfig = velocitySectionField.get(proxiesConfiguration);
|
||||
|
||||
return velocityConfig.getClass().getDeclaredField("enabled").getBoolean(velocityConfig);
|
||||
} catch (ClassNotFoundException classNotFoundException) {
|
||||
// try again using the older Paper configuration, because the old class file still exists in newer versions
|
||||
if (isProxySupported("com.destroystokyo.paper.PaperConfig", "velocitySupport")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean detectProxy() {
|
||||
try {
|
||||
ServerConfiguration.class.getDeclaredMethod("isProxyEnabled");
|
||||
return Bukkit.getServerConfig().isProxyEnabled();
|
||||
} catch (NoClassDefFoundError | NoSuchMethodException noSuchClassMethodEx) {
|
||||
// Ignore continue below
|
||||
}
|
||||
|
||||
try {
|
||||
if (isProxySupported("org.spigotmc.SpigotConfig", "bungee")) {
|
||||
return true;
|
||||
@@ -163,82 +116,40 @@ public class BungeeManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void registerPluginChannels() {
|
||||
Server server = Bukkit.getServer();
|
||||
|
||||
// check for incoming messages from the bungeecord version of this plugin
|
||||
String groupId = plugin.getName();
|
||||
String forceChannel = NamespaceKey.getCombined(groupId, LoginActionMessage.FORCE_CHANNEL);
|
||||
server.getMessenger().registerIncomingPluginChannel(plugin, forceChannel, new BungeeListener(plugin));
|
||||
|
||||
// outgoing
|
||||
String successChannel = new NamespaceKey(groupId, SUCCESS_CHANNEL).getCombinedName();
|
||||
String changeChannel = new NamespaceKey(groupId, CHANGE_CHANNEL).getCombinedName();
|
||||
server.getMessenger().registerOutgoingPluginChannel(plugin, successChannel);
|
||||
server.getMessenger().registerOutgoingPluginChannel(plugin, changeChannel);
|
||||
private boolean isProxySupported(String className, String fieldName)
|
||||
throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
|
||||
return Class.forName(className).getDeclaredField(fieldName).getBoolean(null);
|
||||
}
|
||||
|
||||
private Set<UUID> loadBungeeCordIds() {
|
||||
Path proxiesFile = plugin.getPluginFolder().resolve(FILE_NAME);
|
||||
Path legacyFile = plugin.getPluginFolder().resolve(LEGACY_FILE_NAME);
|
||||
private boolean isVelocityEnabled()
|
||||
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException, ClassNotFoundException,
|
||||
NoSuchMethodException, InvocationTargetException {
|
||||
try {
|
||||
if (Files.notExists(proxiesFile)) {
|
||||
if (Files.exists(legacyFile)) {
|
||||
Files.move(legacyFile, proxiesFile);
|
||||
}
|
||||
Class<?> globalConfig = Class.forName("io.papermc.paper.configuration.GlobalConfiguration");
|
||||
Object global = globalConfig.getDeclaredMethod("get").invoke(null);
|
||||
Object proxiesConfiguration = global.getClass().getDeclaredField("proxies").get(global);
|
||||
|
||||
if (Files.notExists(legacyFile)) {
|
||||
Files.createFile(proxiesFile);
|
||||
}
|
||||
}
|
||||
Field velocitySectionField = proxiesConfiguration.getClass().getDeclaredField("velocity");
|
||||
Object velocityConfig = velocitySectionField.get(proxiesConfiguration);
|
||||
|
||||
Files.deleteIfExists(legacyFile);
|
||||
try (Stream<String> lines = Files.lines(proxiesFile)) {
|
||||
return lines.map(String::trim).map(UUID::fromString).collect(toSet());
|
||||
return velocityConfig.getClass().getDeclaredField("enabled").getBoolean(velocityConfig);
|
||||
} catch (ClassNotFoundException classNotFoundException) {
|
||||
// try again using the older Paper configuration, because the old class file still exists in newer versions
|
||||
if (isProxySupported("com.destroystokyo.paper.PaperConfig", "velocitySupport")) {
|
||||
return true;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
plugin.getLog().error("Failed to read proxies", ex);
|
||||
} catch (Exception ex) {
|
||||
plugin.getLog().error("Failed to retrieve proxy Id. Disabling BungeeCord support", ex);
|
||||
}
|
||||
|
||||
return Collections.emptySet();
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isProxyAllowed(UUID proxyId) {
|
||||
return proxyIds != null && proxyIds.contains(proxyId);
|
||||
}
|
||||
public void sendPluginMessage(PluginMessageRecipient player, ChannelMessage message) {
|
||||
if (player != null) {
|
||||
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
|
||||
message.writeTo(dataOutput);
|
||||
|
||||
/**
|
||||
* Mark the event to be fired including the task delay.
|
||||
*
|
||||
* @param player joining player
|
||||
*/
|
||||
public synchronized void markJoinEventFired(Player player) {
|
||||
firedJoinEvents.add(player.getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the event fired including with the task delay. This necessary to restore the order of processing the
|
||||
* BungeeCord messages after the PlayerJoinEvent fires including the delay.
|
||||
* <p>
|
||||
* If the join event fired, the delay exceeded, but it ran earlier and couldn't find the recently started login
|
||||
* session. If not fired, we can start a new force login task. This will still match the requirement that we wait
|
||||
* a certain time after the player join event fired.
|
||||
*
|
||||
* @param player joining player
|
||||
* @return event fired including delay
|
||||
*/
|
||||
public synchronized boolean didJoinEventFired(Player player) {
|
||||
return firedJoinEvents.contains(player.getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Player quit clean up
|
||||
*
|
||||
* @param player joining player
|
||||
*/
|
||||
public synchronized void cleanup(Player player) {
|
||||
firedJoinEvents.remove(player.getUniqueId());
|
||||
NamespaceKey channel = new NamespaceKey(plugin.getName(), message.getChannelName());
|
||||
player.sendPluginMessage(plugin, channel.getCombinedName(), dataOutput.toByteArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2024 games647 and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.auth.proxy;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.auth.FloodgateAuthTask;
|
||||
import com.github.games647.fastlogin.bukkit.auth.ForceLoginTask;
|
||||
import com.github.games647.fastlogin.core.hooks.bedrock.FloodgateService;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerLoginEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.metadata.Metadatable;
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
|
||||
public class ProxyConnectionListener implements Listener {
|
||||
|
||||
private static final long DELAY_LOGIN = 20L / 2;
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
private final ProxyVerifier verifier;
|
||||
|
||||
public ProxyConnectionListener(FastLoginBukkit plugin, ProxyVerifier verifier) {
|
||||
this.plugin = plugin;
|
||||
this.verifier = verifier;
|
||||
}
|
||||
|
||||
private void removeBlockedStatus(Metadatable player) {
|
||||
player.removeMetadata(plugin.getName(), plugin);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
|
||||
removeBlockedStatus(loginEvent.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlayerJoin(PlayerJoinEvent joinEvent) {
|
||||
Player player = joinEvent.getPlayer();
|
||||
|
||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
delayForceLogin(player);
|
||||
// delay the login process to let auth plugins initialize the player
|
||||
// Magic number however as there is no direct event from those plugins
|
||||
}, DELAY_LOGIN);
|
||||
}
|
||||
|
||||
private void delayForceLogin(Player player) {
|
||||
// session exists so the player is ready for force login
|
||||
// cases: Paper (firing BungeeCord message before PlayerJoinEvent) or not running BungeeCord and already
|
||||
// having the login session from the login process
|
||||
BukkitLoginSession session = plugin.getSession(player.spigot().getRawAddress());
|
||||
|
||||
if (session == null) {
|
||||
// Floodgate players usually don't have a session at this point
|
||||
// exception: if force login by bungee message had been delayed
|
||||
FloodgateService floodgateService = plugin.getFloodgateService();
|
||||
if (floodgateService != null) {
|
||||
FloodgatePlayer floodgatePlayer = floodgateService.getBedrockPlayer(player.getUniqueId());
|
||||
if (floodgatePlayer != null) {
|
||||
Runnable floodgateAuthTask = new FloodgateAuthTask(plugin.getCore(), player, floodgatePlayer);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, floodgateAuthTask);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String sessionId = plugin.getSessionId(player.spigot().getRawAddress());
|
||||
plugin.getLog().info("No on-going login session for player: {} with ID {}. ", player, sessionId);
|
||||
plugin.getLog().info("Setups using Minecraft proxies will start delayed "
|
||||
+ "when the command from the proxy is received");
|
||||
} else {
|
||||
Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player, session);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, forceLoginTask);
|
||||
}
|
||||
|
||||
verifier.markJoinEventFired(player);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent quitEvent) {
|
||||
Player player = quitEvent.getPlayer();
|
||||
|
||||
removeBlockedStatus(player);
|
||||
verifier.cleanup(player);
|
||||
}
|
||||
}
|
||||
@@ -23,11 +23,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener;
|
||||
package com.github.games647.fastlogin.bukkit.auth.proxy;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.task.ForceLoginTask;
|
||||
import com.github.games647.fastlogin.bukkit.auth.ForceLoginTask;
|
||||
import com.github.games647.fastlogin.core.PremiumStatus;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.github.games647.fastlogin.core.message.LoginActionMessage;
|
||||
@@ -47,12 +47,14 @@ import java.util.UUID;
|
||||
* This class also receives the plugin message from the bungeecord version of this plugin in order to get notified if
|
||||
* the connection is in online mode.
|
||||
*/
|
||||
public class BungeeListener implements PluginMessageListener {
|
||||
public class ProxyListener implements PluginMessageListener {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
private final ProxyVerifier verifier;
|
||||
|
||||
public BungeeListener(FastLoginBukkit plugin) {
|
||||
public ProxyListener(FastLoginBukkit plugin, ProxyVerifier verifier) {
|
||||
this.plugin = plugin;
|
||||
this.verifier = verifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -79,7 +81,7 @@ public class BungeeListener implements PluginMessageListener {
|
||||
plugin.getLog().warn("Received message {} from a blocked player {}", loginMessage, targetPlayer);
|
||||
} else {
|
||||
UUID sourceId = loginMessage.getProxyId();
|
||||
if (plugin.getBungeeManager().isProxyAllowed(sourceId)) {
|
||||
if (verifier.isProxyAllowed(sourceId)) {
|
||||
readMessage(targetPlayer, loginMessage);
|
||||
} else {
|
||||
plugin.getLog().warn("Received proxy id: {} that doesn't exist in the proxy file", sourceId);
|
||||
@@ -127,7 +129,7 @@ public class BungeeListener implements PluginMessageListener {
|
||||
plugin.putSession(player.spigot().getRawAddress(), session);
|
||||
|
||||
// only start a new login task if the join event fired earlier. This event then didn't
|
||||
boolean result = plugin.getBungeeManager().didJoinEventFired(player);
|
||||
boolean result = verifier.didJoinEventFired(player);
|
||||
plugin.getLog().info("Delaying force login until join event fired?: {}", result);
|
||||
if (result) {
|
||||
Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player, session);
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2024 games647 and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.auth.proxy;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
|
||||
public class ProxyVerifier {
|
||||
|
||||
private static final String LEGACY_FILE_NAME = "proxy-whitelist.txt";
|
||||
private static final String FILE_NAME = "allowed-proxies.txt";
|
||||
|
||||
//null if proxies allowed list is empty so bungeecord support is disabled
|
||||
private Set<UUID> proxyIds;
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
private final Collection<UUID> firedJoinEvents = new HashSet<>();
|
||||
|
||||
public ProxyVerifier(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
//remove old blocked status
|
||||
Bukkit.getOnlinePlayers().forEach(player -> player.removeMetadata(plugin.getName(), plugin));
|
||||
}
|
||||
|
||||
public void loadSecrets() {
|
||||
proxyIds = loadBungeeCordIds();
|
||||
if (proxyIds.isEmpty()) {
|
||||
plugin.getLog().info("No valid IDs found. Minecraft proxy support cannot work in the current state");
|
||||
}
|
||||
}
|
||||
|
||||
private Set<UUID> loadBungeeCordIds() {
|
||||
Path proxiesFile = plugin.getPluginFolder().resolve(FILE_NAME);
|
||||
Path legacyFile = plugin.getPluginFolder().resolve(LEGACY_FILE_NAME);
|
||||
try {
|
||||
if (Files.notExists(proxiesFile)) {
|
||||
if (Files.exists(legacyFile)) {
|
||||
Files.move(legacyFile, proxiesFile);
|
||||
}
|
||||
|
||||
if (Files.notExists(legacyFile)) {
|
||||
Files.createFile(proxiesFile);
|
||||
}
|
||||
}
|
||||
|
||||
Files.deleteIfExists(legacyFile);
|
||||
try (Stream<String> lines = Files.lines(proxiesFile)) {
|
||||
return lines.map(String::trim).map(UUID::fromString).collect(toSet());
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
plugin.getLog().error("Failed to read proxies", ex);
|
||||
} catch (Exception ex) {
|
||||
plugin.getLog().error("Failed to retrieve proxy Id. Disabling BungeeCord support", ex);
|
||||
}
|
||||
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
public boolean isProxyAllowed(UUID proxyId) {
|
||||
return proxyIds != null && proxyIds.contains(proxyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the event to be fired including the task delay.
|
||||
*
|
||||
* @param player joining player
|
||||
*/
|
||||
public synchronized void markJoinEventFired(Player player) {
|
||||
firedJoinEvents.add(player.getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the event fired including with the task delay. This necessary to restore the order of processing the
|
||||
* BungeeCord messages after the PlayerJoinEvent fires including the delay.
|
||||
* <p>
|
||||
* If the join event fired, the delay exceeded, but it ran earlier and couldn't find the recently started login
|
||||
* session. If not fired, we can start a new force login task. This will still match the requirement that we wait
|
||||
* a certain time after the player join event fired.
|
||||
*
|
||||
* @param player joining player
|
||||
* @return event fired including delay
|
||||
*/
|
||||
public synchronized boolean didJoinEventFired(Player player) {
|
||||
return firedJoinEvents.contains(player.getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Player quit clean up
|
||||
*
|
||||
* @param player joining player
|
||||
*/
|
||||
public synchronized void cleanup(Player player) {
|
||||
firedJoinEvents.remove(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,6 @@ 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;
|
||||
@@ -58,7 +57,6 @@ public class CrackedCommand extends ToggleCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = (Player) sender;
|
||||
if (forwardCrackedCommand(sender, sender.getName())) {
|
||||
return;
|
||||
}
|
||||
@@ -73,16 +71,7 @@ public class CrackedCommand extends ToggleCommand {
|
||||
plugin.getScheduler().runAsync(() -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
plugin.getServer().getPluginManager().callEvent(
|
||||
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);
|
||||
}
|
||||
});
|
||||
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_OTHER));
|
||||
});
|
||||
} else {
|
||||
plugin.getCore().sendLocaleMessage("not-premium", sender);
|
||||
@@ -115,7 +104,7 @@ public class CrackedCommand extends ToggleCommand {
|
||||
plugin.getScheduler().runAsync(() -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
plugin.getServer().getPluginManager().callEvent(
|
||||
new BukkitFastLoginPremiumToggleEvent(sender, profile, PremiumToggleReason.COMMAND_OTHER));
|
||||
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_OTHER));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2024 games647 and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.command;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
|
||||
public class DeleteCommand implements TabExecutor {
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public DeleteCommand(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the command to delete profiles.
|
||||
*/
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
|
||||
if (!sender.hasPermission(command.getPermission())) {
|
||||
plugin.getCore().sendLocaleMessage("no-permission", sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (plugin.getBungeeManager().isEnabled()) {
|
||||
sender.sendMessage("Error: Cannot delete profile entries when using BungeeCord!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (args.length < 1) {
|
||||
sender.sendMessage("Error: Must supply username to delete!");
|
||||
return false;
|
||||
}
|
||||
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
int count = plugin.getCore().getStorage().deleteProfile(args[0]);
|
||||
if (!(sender instanceof ConsoleCommandSender)) {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (count == 0) {
|
||||
sender.sendMessage("Error: No profile entries found!");
|
||||
} else {
|
||||
sender.sendMessage("Deleted " + count + " matching profile entries");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
List<String> list = new ArrayList<>();
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
if (p.getName().toLowerCase().startsWith(args[0])) {
|
||||
list.add(p.getName());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -64,19 +64,18 @@ public class PremiumCommand extends ToggleCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID id = ((Player) sender).getUniqueId();
|
||||
if (plugin.getConfig().getBoolean("premium-warning") && !plugin.getPendingConfirms().contains(id)) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("premium-warning"));
|
||||
plugin.getPendingConfirms().add(id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (forwardPremiumCommand(sender, sender.getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getCore().getPendingConfirms().remove(id);
|
||||
plugin.getPendingConfirms().remove(id);
|
||||
//todo: load async
|
||||
StoredProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
|
||||
if (profile.isOnlinemodePreferred()) {
|
||||
@@ -87,17 +86,10 @@ public class PremiumCommand extends ToggleCommand {
|
||||
plugin.getScheduler().runAsync(() -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
plugin.getServer().getPluginManager().callEvent(
|
||||
new BukkitFastLoginPremiumToggleEvent(sender, profile, PremiumToggleReason.COMMAND_SELF)
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_SELF));
|
||||
});
|
||||
|
||||
plugin.getCore().sendLocaleMessage("add-premium", sender);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,8 +117,7 @@ public class PremiumCommand extends ToggleCommand {
|
||||
plugin.getScheduler().runAsync(() -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
plugin.getServer().getPluginManager().callEvent(
|
||||
new BukkitFastLoginPremiumToggleEvent(sender, profile, PremiumToggleReason.COMMAND_OTHER)
|
||||
);
|
||||
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_OTHER));
|
||||
});
|
||||
|
||||
plugin.getCore().sendLocaleMessage("add-premium-other", sender);
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
package com.github.games647.fastlogin.bukkit.command;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.auth.proxy.ProxyAuthentication;
|
||||
import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
|
||||
import com.github.games647.fastlogin.core.message.ChannelMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -55,7 +56,7 @@ public abstract class ToggleCommand implements CommandExecutor {
|
||||
}
|
||||
|
||||
protected boolean forwardBungeeCommand(CommandSender sender, String target, boolean activate) {
|
||||
if (plugin.getBungeeManager().isEnabled()) {
|
||||
if (plugin.getBackend() instanceof ProxyAuthentication) {
|
||||
sendBungeeActivateMessage(sender, target, activate);
|
||||
plugin.getCore().sendLocaleMessage("wait-on-proxy", sender);
|
||||
return true;
|
||||
|
||||
@@ -27,7 +27,6 @@ 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;
|
||||
@@ -35,15 +34,11 @@ 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(CommandSender invoker, StoredProfile profile, PremiumToggleReason reason) {
|
||||
public BukkitFastLoginPremiumToggleEvent(StoredProfile profile, PremiumToggleReason reason) {
|
||||
super(true);
|
||||
|
||||
this.invoker = invoker;
|
||||
this.profile = profile;
|
||||
this.reason = reason;
|
||||
}
|
||||
@@ -53,13 +48,6 @@ 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;
|
||||
|
||||
@@ -23,16 +23,9 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.task;
|
||||
package com.github.games647.fastlogin.bukkit.hook;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.hook.AuthMeHook;
|
||||
import com.github.games647.fastlogin.bukkit.hook.CrazyLoginHook;
|
||||
import com.github.games647.fastlogin.bukkit.hook.LogItHook;
|
||||
import com.github.games647.fastlogin.bukkit.hook.LoginSecurityHook;
|
||||
import com.github.games647.fastlogin.bukkit.hook.UltraAuthHook;
|
||||
import com.github.games647.fastlogin.bukkit.hook.XAuthHook;
|
||||
import com.github.games647.fastlogin.bukkit.hook.PasskyHook;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -52,19 +45,7 @@ public class DelayedAuthHook implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
boolean hookFound = isHookFound();
|
||||
if (plugin.getBungeeManager().isEnabled()) {
|
||||
plugin.getLog().info("BungeeCord setting detected. No auth plugin is required");
|
||||
} else if (!hookFound) {
|
||||
plugin.getLog().warn("No auth plugin were found by this plugin "
|
||||
+ "(other plugins could hook into this after the initialization of this plugin)"
|
||||
+ "and BungeeCord is deactivated. "
|
||||
+ "Either one or both of the checks have to pass in order to use this plugin");
|
||||
}
|
||||
|
||||
if (hookFound) {
|
||||
plugin.markInitialized();
|
||||
}
|
||||
plugin.setInitialized(isHookFound());
|
||||
}
|
||||
|
||||
private boolean isHookFound() {
|
||||
@@ -95,7 +76,7 @@ public class DelayedAuthHook implements Runnable {
|
||||
try {
|
||||
List<Class<? extends AuthPlugin<Player>>> hooks = Arrays.asList(AuthMeHook.class,
|
||||
CrazyLoginHook.class, LogItHook.class, LoginSecurityHook.class, UltraAuthHook.class,
|
||||
XAuthHook.class, PasskyHook.class);
|
||||
XAuthHook.class);
|
||||
|
||||
for (Class<? extends AuthPlugin<Player>> clazz : hooks) {
|
||||
String pluginName = clazz.getSimpleName();
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2024 games647 and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.hook;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.rabbitcomapny.api.Identifier;
|
||||
import com.rabbitcomapny.api.LoginResult;
|
||||
import com.rabbitcomapny.api.PasskyAPI;
|
||||
import com.rabbitcomapny.api.RegisterResult;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class PasskyHook implements AuthPlugin<Player> {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public PasskyHook(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
LoginResult result = PasskyAPI.forceLogin(new Identifier(player), true);
|
||||
if (!result.success) {
|
||||
plugin.getLog().error("Failed to force login {} via Passky: {}", player.getName(), result.status);
|
||||
}
|
||||
|
||||
return result.success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
RegisterResult result = PasskyAPI.forceRegister(new Identifier(player), password, true);
|
||||
if (!result.success) {
|
||||
plugin.getLog().error("Failed to register {} via Passky: {}", player.getName(), result.status);
|
||||
}
|
||||
|
||||
return result.success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) {
|
||||
return PasskyAPI.isRegistered(new Identifier(playerName));
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2024 games647 and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocolsupport;
|
||||
|
||||
import com.github.games647.craftapi.UUIDAdapter;
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService.Action;
|
||||
import com.github.games647.fastlogin.core.shared.JoinManagement;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import protocolsupport.api.events.ConnectionCloseEvent;
|
||||
import protocolsupport.api.events.PlayerLoginStartEvent;
|
||||
import protocolsupport.api.events.PlayerProfileCompleteEvent;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ProtocolSupportListener extends JoinManagement<Player, CommandSender, ProtocolLoginSource>
|
||||
implements Listener {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
private final AntiBotService antiBotService;
|
||||
|
||||
public ProtocolSupportListener(FastLoginBukkit plugin, AntiBotService antiBotService) {
|
||||
super(plugin.getCore(), plugin.getCore().getAuthPluginHook(), plugin.getBedrockService());
|
||||
|
||||
this.plugin = plugin;
|
||||
this.antiBotService = antiBotService;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onLoginStart(PlayerLoginStartEvent loginStartEvent) {
|
||||
if (loginStartEvent.isLoginDenied() || plugin.getCore().getAuthPluginHook() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String username = loginStartEvent.getConnection().getProfile().getName();
|
||||
InetSocketAddress address = loginStartEvent.getConnection().getRawAddress();
|
||||
plugin.getLog().info("Incoming login request for {} from {}", username, address);
|
||||
|
||||
Action action = antiBotService.onIncomingConnection(address, username);
|
||||
switch (action) {
|
||||
case Ignore:
|
||||
// just ignore
|
||||
return;
|
||||
case Block:
|
||||
String message = plugin.getCore().getMessage("kick-antibot");
|
||||
loginStartEvent.denyLogin(message);
|
||||
break;
|
||||
case Continue:
|
||||
default:
|
||||
//remove old data every time on a new login in order to keep the session only for one person
|
||||
plugin.removeSession(address);
|
||||
|
||||
ProtocolLoginSource source = new ProtocolLoginSource(loginStartEvent);
|
||||
super.onLogin(username, source);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onConnectionClosed(ConnectionCloseEvent closeEvent) {
|
||||
InetSocketAddress address = closeEvent.getConnection().getRawAddress();
|
||||
plugin.removeSession(address);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPropertiesResolve(PlayerProfileCompleteEvent profileCompleteEvent) {
|
||||
InetSocketAddress address = profileCompleteEvent.getConnection().getRawAddress();
|
||||
|
||||
BukkitLoginSession session = plugin.getSession(address);
|
||||
|
||||
if (session != null && profileCompleteEvent.getConnection().getProfile().isOnlineMode()) {
|
||||
session.setVerifiedPremium(true);
|
||||
|
||||
if (!plugin.getConfig().getBoolean("premiumUuid")) {
|
||||
String username = Optional.ofNullable(profileCompleteEvent.getForcedName())
|
||||
.orElse(profileCompleteEvent.getConnection().getProfile().getName());
|
||||
profileCompleteEvent.setForcedUUID(UUIDAdapter.generateOfflineId(username));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, ProtocolLoginSource source,
|
||||
StoredProfile profile) {
|
||||
BukkitFastLoginPreLoginEvent event = new BukkitFastLoginPreLoginEvent(username, source, profile);
|
||||
plugin.getServer().getPluginManager().callEvent(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestPremiumLogin(ProtocolLoginSource source, StoredProfile profile, String username,
|
||||
boolean registered) {
|
||||
source.enableOnlinemode();
|
||||
|
||||
String ip = source.getAddress().getAddress().getHostAddress();
|
||||
plugin.getCore().addLoginAttempt(ip, username);
|
||||
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(username, registered, profile);
|
||||
plugin.putSession(source.getAddress(), playerSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startCrackedSession(ProtocolLoginSource source, StoredProfile profile, String username) {
|
||||
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
||||
plugin.putSession(source.getAddress(), loginSession);
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,6 @@ softdepend:
|
||||
- LogIt
|
||||
- UltraAuth
|
||||
- xAuth
|
||||
- Passky
|
||||
|
||||
commands:
|
||||
${project.parent.name}:
|
||||
@@ -45,11 +44,6 @@ commands:
|
||||
usage: /<command> [player]
|
||||
permission: ${project.artifactId}.command.cracked
|
||||
|
||||
fldelete:
|
||||
description: 'Delete player profile data'
|
||||
usage: /<command> [player]
|
||||
permission: ${project.artifactId}.command.delete
|
||||
|
||||
permissions:
|
||||
${project.artifactId}.command.premium:
|
||||
description: 'Label themselves as premium'
|
||||
@@ -68,7 +62,3 @@ permissions:
|
||||
description: 'Label others as cracked'
|
||||
children:
|
||||
${project.artifactId}.command.cracked: true
|
||||
|
||||
${project.artifactId}.command.delete:
|
||||
description: 'Delete other players profile data'
|
||||
default: op
|
||||
@@ -26,7 +26,7 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.github.games647.fastlogin.core.CommonUtil;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import lombok.val;
|
||||
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() {
|
||||
String message = "&x00002a00002b&lText";
|
||||
String msg = CommonUtil.translateColorCodes(message);
|
||||
val message = "&x00002a00002b&lText";
|
||||
val msg = CommonUtil.translateColorCodes(message);
|
||||
assertEquals(msg, "§x00002a00002b§lText");
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
BaseComponent[] components = TextComponent.fromLegacyText(msg);
|
||||
String expected = "{\"bold\":true,\"color\":\"#00a00b\",\"text\":\"Text\"}";
|
||||
val components = TextComponent.fromLegacyText(msg);
|
||||
val expected = "{\"bold\":true,\"color\":\"#00a00b\",\"text\":\"Text\"}";
|
||||
//noinspection deprecation
|
||||
assertEquals(ComponentSerializer.toString(components), expected);
|
||||
}
|
||||
|
||||
@@ -23,11 +23,12 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
package com.github.games647.fastlogin.bukkit.auth.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;
|
||||
@@ -36,7 +37,7 @@ public class Base64Adapter extends TypeAdapter<byte[]> {
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, byte[] value) throws IOException {
|
||||
String encoded = Base64.getEncoder().encodeToString(value);
|
||||
val encoded = Base64.getEncoder().encodeToString(value);
|
||||
out.value(encoded);
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
package com.github.games647.fastlogin.bukkit.auth.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.github.games647.fastlogin.bukkit.auth.protocollib.SignatureTestData.SignatureData;
|
||||
import com.github.games647.fastlogin.bukkit.auth.protocollib.packet.ClientPublicKey;
|
||||
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,7 +51,6 @@ 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;
|
||||
|
||||
@@ -61,7 +60,7 @@ class EncryptionUtilTest {
|
||||
|
||||
@Test
|
||||
void testVerifyToken() {
|
||||
Random random = ThreadLocalRandom.current();
|
||||
@SuppressWarnings("SharedThreadLocalRandom") val random = ThreadLocalRandom.current();
|
||||
byte[] token = EncryptionUtil.generateVerifyToken(random);
|
||||
|
||||
assertAll(
|
||||
@@ -89,10 +88,10 @@ class EncryptionUtilTest {
|
||||
|
||||
@Test
|
||||
void testExpiredClientKey() throws Exception {
|
||||
ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
|
||||
val clientKey = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
|
||||
|
||||
// Client expires at the exact second mentioned, so use it for verification
|
||||
Instant expiredTimestamp = clientKey.expiry();
|
||||
val expiredTimestamp = clientKey.expiry();
|
||||
assertFalse(EncryptionUtil.verifyClientKey(clientKey, expiredTimestamp, null));
|
||||
}
|
||||
|
||||
@@ -106,7 +105,7 @@ class EncryptionUtilTest {
|
||||
"client_keys/invalid_wrong_signature.json"
|
||||
})
|
||||
void testInvalidClientKey(String clientKeySource) throws Exception {
|
||||
ClientPublicKey clientKey = ResourceLoader.loadClientKey(clientKeySource);
|
||||
val clientKey = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey(clientKeySource);
|
||||
Instant expireTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
|
||||
|
||||
assertFalse(EncryptionUtil.verifyClientKey(clientKey, expireTimestamp, null));
|
||||
@@ -114,34 +113,34 @@ class EncryptionUtilTest {
|
||||
|
||||
@Test
|
||||
void testValidClientKey() throws Exception {
|
||||
ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
|
||||
Instant verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
|
||||
val clientKey = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
|
||||
val verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
|
||||
|
||||
assertTrue(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValid191ClientKey() throws Exception {
|
||||
ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key_19_1.json");
|
||||
Instant verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
|
||||
val clientKey = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey("client_keys/valid_public_key_19_1.json");
|
||||
val verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
|
||||
|
||||
UUID ownerPremiumId = UUID.fromString("0aaa2c13-922a-411b-b655-9b8c08404695");
|
||||
val ownerPremiumId = UUID.fromString("0aaa2c13-922a-411b-b655-9b8c08404695");
|
||||
assertTrue(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp, ownerPremiumId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIncorrect191ClientOwner() throws Exception {
|
||||
ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key_19_1.json");
|
||||
Instant verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
|
||||
val clientKey = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey("client_keys/valid_public_key_19_1.json");
|
||||
val verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
|
||||
|
||||
UUID ownerPremiumId = UUID.fromString("61699b2e-d327-4a01-9f1e-0ea8c3f06bc6");
|
||||
val ownerPremiumId = UUID.fromString("61699b2e-d327-4a01-9f1e-0ea8c3f06bc6");
|
||||
assertFalse(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp, ownerPremiumId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDecryptSharedSecret() throws Exception {
|
||||
KeyPair serverPair = EncryptionUtil.generateKeyPair();
|
||||
PublicKey serverPK = serverPair.getPublic();
|
||||
val serverPK = serverPair.getPublic();
|
||||
|
||||
SecretKey secretKey = generateSharedKey();
|
||||
byte[] encryptedSecret = encrypt(serverPK, secretKey.getEncoded());
|
||||
@@ -153,7 +152,7 @@ class EncryptionUtilTest {
|
||||
private static byte[] encrypt(PublicKey receiverKey, byte... message)
|
||||
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
|
||||
IllegalBlockSizeException, BadPaddingException {
|
||||
Cipher encryptCipher = Cipher.getInstance(receiverKey.getAlgorithm());
|
||||
val encryptCipher = Cipher.getInstance(receiverKey.getAlgorithm());
|
||||
encryptCipher.init(Cipher.ENCRYPT_MODE, receiverKey);
|
||||
return encryptCipher.doFinal(message);
|
||||
}
|
||||
@@ -169,9 +168,9 @@ class EncryptionUtilTest {
|
||||
|
||||
@Test
|
||||
void testServerIdHash() throws Exception {
|
||||
String serverId = "";
|
||||
SecretKeySpec sharedSecret = generateSharedKey();
|
||||
PublicKey serverPK = ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key();
|
||||
val serverId = "";
|
||||
val sharedSecret = generateSharedKey();
|
||||
val serverPK = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key();
|
||||
|
||||
String sessionHash = getServerHash(serverId, sharedSecret, serverPK);
|
||||
assertEquals(EncryptionUtil.getServerIdHashString(serverId, sharedSecret, serverPK), sessionHash);
|
||||
@@ -186,7 +185,7 @@ class EncryptionUtilTest {
|
||||
// sha1.update(server's encoded public key from Encryption Request)
|
||||
// hash := sha1.hexdigest() # String of hex characters
|
||||
@SuppressWarnings("deprecation")
|
||||
Hasher hasher = Hashing.sha1().newHasher();
|
||||
val hasher = Hashing.sha1().newHasher();
|
||||
hasher.putString(serverId, StandardCharsets.US_ASCII);
|
||||
hasher.putBytes(sharedSecret.getEncoded());
|
||||
hasher.putBytes(serverPK.getEncoded());
|
||||
@@ -199,9 +198,9 @@ class EncryptionUtilTest {
|
||||
|
||||
@Test
|
||||
void testServerIdHashWrongSecret() throws Exception {
|
||||
String serverId = "";
|
||||
SecretKeySpec sharedSecret = generateSharedKey();
|
||||
PublicKey serverPK = ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key();
|
||||
val serverId = "";
|
||||
val sharedSecret = generateSharedKey();
|
||||
val serverPK = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key();
|
||||
|
||||
String sessionHash = getServerHash(serverId, sharedSecret, serverPK);
|
||||
assertNotEquals(EncryptionUtil.getServerIdHashString("", generateSharedKey(), serverPK), sessionHash);
|
||||
@@ -209,18 +208,18 @@ class EncryptionUtilTest {
|
||||
|
||||
@Test
|
||||
void testServerIdHashWrongServerKey() {
|
||||
String serverId = "";
|
||||
SecretKeySpec sharedSecret = generateSharedKey();
|
||||
PublicKey serverPK = EncryptionUtil.generateKeyPair().getPublic();
|
||||
val serverId = "";
|
||||
val sharedSecret = generateSharedKey();
|
||||
val serverPK = EncryptionUtil.generateKeyPair().getPublic();
|
||||
|
||||
String sessionHash = getServerHash(serverId, sharedSecret, serverPK);
|
||||
PublicKey wrongPK = EncryptionUtil.generateKeyPair().getPublic();
|
||||
val wrongPK = EncryptionUtil.generateKeyPair().getPublic();
|
||||
assertNotEquals(EncryptionUtil.getServerIdHashString("", sharedSecret, wrongPK), sessionHash);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidSignedNonce() throws Exception {
|
||||
ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
|
||||
ClientPublicKey clientKey = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
|
||||
SignatureTestData testData = SignatureTestData.fromResource("signature/valid_signature.json");
|
||||
assertTrue(verifySignedNonce(testData, clientKey));
|
||||
}
|
||||
@@ -232,7 +231,7 @@ class EncryptionUtilTest {
|
||||
"signature/incorrect_signature.json",
|
||||
})
|
||||
void testIncorrectNonce(String signatureSource) throws Exception {
|
||||
ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
|
||||
ClientPublicKey clientKey = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
|
||||
SignatureTestData testData = SignatureTestData.fromResource(signatureSource);
|
||||
assertFalse(verifySignedNonce(testData, clientKey));
|
||||
}
|
||||
@@ -240,7 +239,7 @@ class EncryptionUtilTest {
|
||||
@Test
|
||||
void testWrongPublicKeySigned() throws Exception {
|
||||
// load a different public key
|
||||
ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/invalid_wrong_key.json");
|
||||
ClientPublicKey clientKey = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey("client_keys/invalid_wrong_key.json");
|
||||
SignatureTestData testData = SignatureTestData.fromResource("signature/valid_signature.json");
|
||||
assertFalse(verifySignedNonce(testData, clientKey));
|
||||
}
|
||||
@@ -258,8 +257,8 @@ class EncryptionUtilTest {
|
||||
@Test
|
||||
void testNonce() throws Exception {
|
||||
byte[] expected = {1, 2, 3, 4};
|
||||
KeyPair serverKey = EncryptionUtil.generateKeyPair();
|
||||
byte[] encryptedNonce = encrypt(serverKey.getPublic(), expected);
|
||||
val serverKey = EncryptionUtil.generateKeyPair();
|
||||
val encryptedNonce = encrypt(serverKey.getPublic(), expected);
|
||||
|
||||
assertTrue(EncryptionUtil.verifyNonce(expected, serverKey.getPrivate(), encryptedNonce));
|
||||
}
|
||||
@@ -267,19 +266,19 @@ class EncryptionUtilTest {
|
||||
@Test
|
||||
void testNonceIncorrect() throws Exception {
|
||||
byte[] expected = {1, 2, 3, 4};
|
||||
KeyPair serverKey = EncryptionUtil.generateKeyPair();
|
||||
val serverKey = EncryptionUtil.generateKeyPair();
|
||||
|
||||
// flipped first character
|
||||
byte[] encryptedNonce = encrypt(serverKey.getPublic(), new byte[]{0, 2, 3, 4});
|
||||
val 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};
|
||||
KeyPair serverKey = EncryptionUtil.generateKeyPair();
|
||||
val serverKey = EncryptionUtil.generateKeyPair();
|
||||
// generate a new keypair that is different
|
||||
byte[] encryptedNonce = encrypt(EncryptionUtil.generateKeyPair().getPublic(), expected);
|
||||
val encryptedNonce = encrypt(EncryptionUtil.generateKeyPair().getPublic(), expected);
|
||||
|
||||
assertThrows(GeneralSecurityException.class,
|
||||
() -> EncryptionUtil.verifyNonce(expected, serverKey.getPrivate(), encryptedNonce)
|
||||
@@ -289,7 +288,7 @@ class EncryptionUtilTest {
|
||||
@Test
|
||||
void testNonceIncorrectEmpty() {
|
||||
byte[] expected = {1, 2, 3, 4};
|
||||
KeyPair serverKey = EncryptionUtil.generateKeyPair();
|
||||
val serverKey = EncryptionUtil.generateKeyPair();
|
||||
byte[] encryptedNonce = {};
|
||||
|
||||
assertThrows(GeneralSecurityException.class,
|
||||
@@ -23,9 +23,9 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
package com.github.games647.fastlogin.bukkit.auth.protocollib;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
|
||||
import com.github.games647.fastlogin.bukkit.auth.protocollib.packet.ClientPublicKey;
|
||||
import com.google.common.io.Resources;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
@@ -50,6 +50,10 @@ import java.util.Base64;
|
||||
|
||||
public class ResourceLoader {
|
||||
|
||||
private ResourceLoader() {
|
||||
// Utility
|
||||
}
|
||||
|
||||
public static RSAPrivateKey parsePrivateKey(String keySpec)
|
||||
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
try (
|
||||
@@ -23,21 +23,21 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
package com.github.games647.fastlogin.bukkit.auth.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 {
|
||||
URL keyUrl = Resources.getResource(resourceName);
|
||||
String encodedSignature = Resources.toString(keyUrl, StandardCharsets.US_ASCII);
|
||||
val keyUrl = Resources.getResource(resourceName);
|
||||
val encodedSignature = Resources.toString(keyUrl, StandardCharsets.US_ASCII);
|
||||
|
||||
return new Gson().fromJson(encodedSignature, SignatureTestData.class);
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
package com.github.games647.fastlogin.bukkit.auth.protocollib;
|
||||
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
@@ -23,9 +23,10 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.task;
|
||||
package com.github.games647.fastlogin.bukkit.hook;
|
||||
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import lombok.val;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -35,7 +36,7 @@ class DelayedAuthHookTest {
|
||||
|
||||
@Test
|
||||
void createNewReflectiveInstance() throws ReflectiveOperationException {
|
||||
DelayedAuthHook authHook = new DelayedAuthHook(null);
|
||||
val authHook = new DelayedAuthHook(null);
|
||||
assertNotNull(authHook.newInstance(DummyHook.class));
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<version>3.5.3</version>
|
||||
<configuration>
|
||||
<minimizeJar>true</minimizeJar>
|
||||
|
||||
|
||||
@@ -233,9 +233,6 @@ public class ConnectListener implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
// delay sending force command, because Paper will process the login event asynchronously
|
||||
// In this case it means that the force command (plugin message) is already received and processed while
|
||||
// player is still in the login phase and reported to be offline.
|
||||
Runnable loginTask = new ForceLoginTask(plugin.getCore(), player, server, session);
|
||||
plugin.getScheduler().runAsync(loginTask);
|
||||
}
|
||||
@@ -244,6 +241,5 @@ public class ConnectListener implements Listener {
|
||||
public void onDisconnect(PlayerDisconnectEvent disconnectEvent) {
|
||||
ProxiedPlayer player = disconnectEvent.getPlayer();
|
||||
plugin.getSession().remove(player.getPendingConnection());
|
||||
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
@@ -96,15 +95,6 @@ public class PluginMessageListener implements Listener {
|
||||
String playerName = changeMessage.getPlayerName();
|
||||
boolean isSourceInvoker = changeMessage.isSourceInvoker();
|
||||
if (changeMessage.shouldEnable()) {
|
||||
if (playerName.equals(forPlayer.getName()) && plugin.getCore().getConfig().get("premium-warning", true)
|
||||
&& !core.getPendingConfirms().contains(forPlayer.getUniqueId())) {
|
||||
String message = core.getMessage("premium-warning");
|
||||
forPlayer.sendMessage(TextComponent.fromLegacyText(message));
|
||||
core.getPendingConfirms().add(forPlayer.getUniqueId());
|
||||
return;
|
||||
}
|
||||
|
||||
core.getPendingConfirms().remove(forPlayer.getUniqueId());
|
||||
Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, true, isSourceInvoker);
|
||||
plugin.getScheduler().runAsync(task);
|
||||
} else {
|
||||
|
||||
@@ -76,12 +76,7 @@ public class AsyncToggleMessage implements Runnable {
|
||||
? PremiumToggleReason.COMMAND_OTHER : PremiumToggleReason.COMMAND_SELF;
|
||||
core.getPlugin().getProxy().getPluginManager().callEvent(
|
||||
new BungeeFastLoginPremiumToggleEvent(playerProfile, reason));
|
||||
|
||||
if (isPlayerSender && core.getConfig().getBoolean("kick-toggle", true)) {
|
||||
sender.disconnect(TextComponent.fromLegacyText(core.getMessage("remove-premium")));
|
||||
} else {
|
||||
sendMessage("remove-premium");
|
||||
}
|
||||
sendMessage("remove-premium");
|
||||
}
|
||||
|
||||
private void activatePremium() {
|
||||
|
||||
10
core/pom.xml
10
core/pom.xml
@@ -73,7 +73,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.4.2</version>
|
||||
<version>3.4.1</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
@@ -125,7 +125,7 @@
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>4.0.3</version>
|
||||
<version>5.1.0</version>
|
||||
<exclusions>
|
||||
<!-- HikariCP uses an old version of this API that has a typo in the service interface -->
|
||||
<!-- We will use the api provided by the jdk14 dependency -->
|
||||
@@ -140,21 +140,21 @@
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-jdk14</artifactId>
|
||||
<version>2.0.17</version>
|
||||
<version>2.0.13</version>
|
||||
</dependency>
|
||||
|
||||
<!-- snakeyaml is present in Bungee, Spigot, so we could use this independent implementation -->
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-config</artifactId>
|
||||
<version>1.20-R0.2</version>
|
||||
<version>1.20-R0.2-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- This is optional in BungeeCord-config, so we include it here manually -->
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>2.4</version>
|
||||
<version>2.2</version>
|
||||
</dependency>
|
||||
|
||||
<!--Floodgate for Xbox Live Authentication-->
|
||||
|
||||
@@ -27,7 +27,6 @@ package com.github.games647.fastlogin.core;
|
||||
|
||||
import com.github.games647.craftapi.model.auth.Verification;
|
||||
import com.github.games647.craftapi.resolver.MojangResolver;
|
||||
import com.github.games647.craftapi.resolver.Options;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
@@ -43,9 +42,6 @@ import java.util.Optional;
|
||||
*/
|
||||
public class ProxyAgnosticMojangResolver extends MojangResolver {
|
||||
|
||||
public ProxyAgnosticMojangResolver(Options options) {
|
||||
super(options);
|
||||
}
|
||||
@Override
|
||||
public Optional<Verification> hasJoined(String username, String serverHash, InetAddress hostIp)
|
||||
throws IOException {
|
||||
|
||||
@@ -63,7 +63,7 @@ public class LoginActionMessage implements ChannelMessage {
|
||||
|
||||
@Override
|
||||
public void readFrom(ByteArrayDataInput input) {
|
||||
this.type = Type.values()[input.readByte()];
|
||||
this.type = Type.values()[input.readInt()];
|
||||
|
||||
this.playerName = input.readUTF();
|
||||
|
||||
@@ -75,7 +75,7 @@ public class LoginActionMessage implements ChannelMessage {
|
||||
|
||||
@Override
|
||||
public void writeTo(ByteArrayDataOutput output) {
|
||||
output.writeByte(type.ordinal());
|
||||
output.writeInt(type.ordinal());
|
||||
|
||||
//Data is sent through a random player. We have to tell the Bukkit version of this plugin the target
|
||||
output.writeUTF(playerName);
|
||||
|
||||
@@ -43,30 +43,9 @@ public abstract class AbstractAsyncScheduler {
|
||||
this.processingPool = processingPool;
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> runAsync(Runnable task) {
|
||||
return CompletableFuture.runAsync(() -> process(task), processingPool).exceptionally(error -> {
|
||||
logger.warn("Error occurred on thread pool", error);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
public abstract CompletableFuture<Void> runAsync(Runnable task);
|
||||
|
||||
public CompletableFuture<Void> runAsyncDelayed(Runnable task, Duration delay) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
currentlyRunning.incrementAndGet();
|
||||
try {
|
||||
Thread.sleep(delay.toMillis());
|
||||
task.run();
|
||||
} catch (InterruptedException interruptedException) {
|
||||
// restore interrupt flag
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
currentlyRunning.getAndDecrement();
|
||||
}
|
||||
}, processingPool).exceptionally(error -> {
|
||||
logger.warn("Error occurred on thread pool", error);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
public abstract CompletableFuture<Void> runAsyncDelayed(Runnable task, Duration delay);
|
||||
|
||||
protected void process(Runnable task) {
|
||||
currentlyRunning.incrementAndGet();
|
||||
|
||||
@@ -27,6 +27,8 @@ package com.github.games647.fastlogin.core.scheduler;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
@@ -40,8 +42,33 @@ public class AsyncScheduler extends AbstractAsyncScheduler {
|
||||
|
||||
public AsyncScheduler(Logger logger, Executor processingPool) {
|
||||
super(logger, processingPool);
|
||||
logger.info("Using legacy platform scheduler for using an older Java version. "
|
||||
+ "Upgrade Java to 21+ for improved performance");
|
||||
logger.info("Using legacy scheduler");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> runAsync(Runnable task) {
|
||||
return CompletableFuture.runAsync(() -> process(task), processingPool).exceptionally(error -> {
|
||||
logger.warn("Error occurred on thread pool", error);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> runAsyncDelayed(Runnable task, Duration delay) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
currentlyRunning.incrementAndGet();
|
||||
try {
|
||||
Thread.sleep(delay.toMillis());
|
||||
process(task);
|
||||
} catch (InterruptedException interruptedException) {
|
||||
// restore interrupt flag
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
currentlyRunning.getAndDecrement();
|
||||
}
|
||||
}, processingPool).exceptionally(error -> {
|
||||
logger.warn("Error occurred on thread pool", error);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
package com.github.games647.fastlogin.core.shared;
|
||||
|
||||
import com.github.games647.craftapi.resolver.MojangResolver;
|
||||
import com.github.games647.craftapi.resolver.Options;
|
||||
import com.github.games647.craftapi.resolver.http.RotatingProxySelector;
|
||||
import com.github.games647.fastlogin.core.CommonUtil;
|
||||
import com.github.games647.fastlogin.core.ProxyAgnosticMojangResolver;
|
||||
@@ -49,9 +48,11 @@ import net.md_5.bungee.config.YamlConfiguration;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.Proxy.Type;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
@@ -60,7 +61,6 @@ import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
@@ -82,7 +82,6 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
Duration.ofMinutes(5), -1
|
||||
);
|
||||
|
||||
private final Collection<UUID> pendingConfirms = new HashSet<>();
|
||||
private final T plugin;
|
||||
|
||||
private MojangResolver resolver;
|
||||
@@ -120,35 +119,29 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
return;
|
||||
}
|
||||
|
||||
Options resolverOptions = new Options();
|
||||
resolverOptions.setMaxNameRequests(config.getInt("mojang-request-limit", 600));
|
||||
// Initialize the resolver based on the config parameter
|
||||
this.resolver = this.config.getBoolean("useProxyAgnosticResolver", false)
|
||||
? new ProxyAgnosticMojangResolver() : new MojangResolver();
|
||||
|
||||
antiBot = createAntiBotService(config.getSection("anti-bot"));
|
||||
Set<Proxy> proxies = config.getStringList("proxies")
|
||||
.stream()
|
||||
.map(proxy -> proxy.split(":"))
|
||||
.map(proxy -> new InetSocketAddress(proxy[0], Integer.parseInt(proxy[1])))
|
||||
.map(sa -> new Proxy(Type.HTTP, sa))
|
||||
.collect(toSet());
|
||||
if (!proxies.isEmpty()) {
|
||||
resolverOptions.setProxySelector(new RotatingProxySelector(proxies));
|
||||
|
||||
Collection<InetAddress> addresses = new HashSet<>();
|
||||
for (String localAddress : config.getStringList("ip-addresses")) {
|
||||
try {
|
||||
addresses.add(InetAddress.getByName(localAddress.replace('-', '.')));
|
||||
} catch (UnknownHostException ex) {
|
||||
plugin.getLog().error("IP-Address is unknown to us", ex);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Not available currently in craftapi?
|
||||
// Collection<InetAddress> addresses = new HashSet<>();
|
||||
// for (String localAddress : config.getStringList("ip-addresses")) {
|
||||
// try {
|
||||
// addresses.add(InetAddress.getByName(localAddress.replace('-', '.')));
|
||||
// } catch (UnknownHostException ex) {
|
||||
// plugin.getLog().error("IP-Address is unknown to us", ex);
|
||||
// }
|
||||
// }
|
||||
// resolver.setOutgoingAddresses(addresses);
|
||||
|
||||
// Initialize the resolver based on the config parameter
|
||||
this.resolver = this.config.getBoolean("useProxyAgnosticResolver", false)
|
||||
? new ProxyAgnosticMojangResolver(resolverOptions) : new MojangResolver(resolverOptions);
|
||||
|
||||
antiBot = createAntiBotService(config.getSection("anti-bot"));
|
||||
resolver.setMaxNameRequests(config.getInt("mojang-request-limit"));
|
||||
resolver.setProxySelector(new RotatingProxySelector(proxies));
|
||||
}
|
||||
|
||||
private AntiBotService createAntiBotService(Configuration botSection) {
|
||||
@@ -168,12 +161,12 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
|
||||
Action action = Action.Ignore;
|
||||
switch (botSection.getString("action", "ignore")) {
|
||||
case "ignore":
|
||||
action = Action.Ignore;
|
||||
break;
|
||||
case "block":
|
||||
action = Action.Block;
|
||||
break;
|
||||
case "ignore":
|
||||
action = Action.Ignore;
|
||||
break;
|
||||
default:
|
||||
plugin.getLog().warn("Invalid anti bot action - defaulting to ignore");
|
||||
}
|
||||
@@ -292,10 +285,6 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
return pendingLogin.remove(ip + username) != null;
|
||||
}
|
||||
|
||||
public Collection<UUID> getPendingConfirms() {
|
||||
return pendingConfirms;
|
||||
}
|
||||
|
||||
public AuthPlugin<P> getAuthPluginHook() {
|
||||
return authPlugin;
|
||||
}
|
||||
|
||||
@@ -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().get("autoLoginFloodgate").toString().toLowerCase(Locale.ROOT);
|
||||
autoRegisterFloodgate = core.getConfig().get("autoRegisterFloodgate").toString().toLowerCase(Locale.ROOT);
|
||||
allowNameConflict = core.getConfig().get("allowFloodgateNameConflict").toString().toLowerCase(Locale.ROOT);
|
||||
autoLoginFloodgate = core.getConfig().getString("autoLoginFloodgate").toLowerCase(Locale.ROOT);
|
||||
autoRegisterFloodgate = core.getConfig().getString("autoRegisterFloodgate").toLowerCase(Locale.ROOT);
|
||||
allowNameConflict = core.getConfig().getString("allowFloodgateNameConflict").toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -109,7 +109,7 @@ public abstract class JoinManagement<P extends C, C, S extends LoginSource> {
|
||||
premiumUUID = core.getResolver().findProfile(username);
|
||||
}
|
||||
|
||||
if (!premiumUUID.isPresent()
|
||||
if (premiumUUID.isPresent()
|
||||
|| (!isNameChanged(source, username, premiumUUID.get())
|
||||
&& !isUsernameAvailable(source, username, profile))) {
|
||||
//nothing detected the player as premium -> start a cracked session
|
||||
|
||||
@@ -29,7 +29,7 @@ import java.net.InetSocketAddress;
|
||||
|
||||
public interface LoginSource {
|
||||
|
||||
void enableOnlinemode() throws Exception;
|
||||
void enableOnlinemode();
|
||||
|
||||
void kick(String message);
|
||||
|
||||
|
||||
@@ -32,9 +32,12 @@ import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public interface PlatformPlugin<C> {
|
||||
|
||||
Pattern PATTERN = Pattern.compile("%nl%");
|
||||
|
||||
String getName();
|
||||
|
||||
Path getPluginFolder();
|
||||
@@ -48,7 +51,7 @@ public interface PlatformPlugin<C> {
|
||||
boolean isPluginInstalled(String name);
|
||||
|
||||
default void sendMultiLineMessage(C receiver, String message) {
|
||||
for (String line : message.split("%nl%")) {
|
||||
for (String line : PATTERN.split(message)) {
|
||||
sendMessage(receiver, line);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,6 @@ public interface AuthStorage {
|
||||
|
||||
StoredProfile loadProfile(UUID uuid);
|
||||
|
||||
int deleteProfile(String name);
|
||||
|
||||
void save(StoredProfile playerProfile);
|
||||
|
||||
void close();
|
||||
|
||||
@@ -65,8 +65,6 @@ public abstract class SQLStorage implements AuthStorage {
|
||||
+ "` WHERE `Name`=? LIMIT 1";
|
||||
protected static final String LOAD_BY_UUID = "SELECT * FROM `" + PREMIUM_TABLE
|
||||
+ "` WHERE `UUID`=? LIMIT 1";
|
||||
protected static final String DELETE_BY_NAME = "DELETE FROM " + PREMIUM_TABLE
|
||||
+ " WHERE `Name` = ?";
|
||||
protected static final String INSERT_PROFILE = "INSERT INTO `" + PREMIUM_TABLE
|
||||
+ "` (`UUID`, `Name`, `Premium`, `Floodgate`, `LastIp`) " + "VALUES (?, ?, ?, ?, ?) ";
|
||||
// limit not necessary here, because it's unique
|
||||
@@ -145,25 +143,6 @@ public abstract class SQLStorage implements AuthStorage {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteProfile(String name) {
|
||||
try (Connection con = dataSource.getConnection();
|
||||
PreparedStatement deleteStmt = con.prepareStatement(DELETE_BY_NAME)) {
|
||||
deleteStmt.setString(1, name);
|
||||
|
||||
int rowsDeleted = deleteStmt.executeUpdate();
|
||||
if (rowsDeleted > 0) {
|
||||
log.info("Deleted {}'s profile data", name);
|
||||
} else {
|
||||
log.info("No profile data found for {}", name);
|
||||
}
|
||||
return rowsDeleted;
|
||||
} catch (SQLException sqlEx) {
|
||||
log.error("Failed to query profile: {}", name, sqlEx);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<StoredProfile> parseResult(ResultSet resultSet) throws SQLException {
|
||||
if (resultSet.next()) {
|
||||
long userId = resultSet.getInt("UserID");
|
||||
|
||||
@@ -163,17 +163,16 @@ public class StoredProfile extends Profile {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
public synchronized boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(o instanceof StoredProfile)) {
|
||||
if (!(other instanceof StoredProfile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
StoredProfile that = (StoredProfile) o;
|
||||
if (!super.equals(o)) {
|
||||
StoredProfile that = (StoredProfile) other;
|
||||
if (!super.equals(other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ package com.github.games647.fastlogin.core.scheduler;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@@ -44,4 +46,33 @@ public class AsyncScheduler extends AbstractAsyncScheduler {
|
||||
|
||||
logger.info("Using optimized green threads with Java 21");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> runAsync(Runnable task) {
|
||||
return CompletableFuture
|
||||
.runAsync(() -> process(task), processingPool)
|
||||
.exceptionally(error -> {
|
||||
logger.warn("Error occurred on thread pool", error);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> runAsyncDelayed(Runnable task, Duration delay) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
currentlyRunning.incrementAndGet();
|
||||
try {
|
||||
Thread.sleep(delay);
|
||||
process(task);
|
||||
} catch (InterruptedException interruptedException) {
|
||||
// restore interrupt flag
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
currentlyRunning.getAndDecrement();
|
||||
}
|
||||
}, processingPool).exceptionally(error -> {
|
||||
logger.warn("Error occurred on thread pool", error);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,14 +150,11 @@ 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 (and not cracked players)
|
||||
# users who actually are the owner of this account. So not by 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
|
||||
@@ -174,23 +171,7 @@ kick-toggle: 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: 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
|
||||
# You are allowed to make 600 requests per 10-minutes (60 per minute)
|
||||
# If you own a big server this value could be too low
|
||||
# Once the limit is reached, new players are always logged in as cracked until the rate-limit is expired.
|
||||
# (to the next ten minutes)
|
||||
#
|
||||
# The limit is IP-wide. If you have multiple IPv4-addresses you specify them here. FastLogin will then use it in
|
||||
# rotating order --> 5 different IP-addresses 5 * 600 per 10 minutes
|
||||
# If this list is empty only the default one will be used
|
||||
#
|
||||
# Lists are created like this:
|
||||
#ip-addresses:
|
||||
# - 192-168-0-2
|
||||
ip-addresses: []
|
||||
useProxyAgnosticResolver: false
|
||||
|
||||
# How many requests should be established to the Mojang API for Name -> UUID requests. Some other plugins as well
|
||||
# as the head Minecraft block make such requests as well. Using this option you can limit the amount requests this
|
||||
|
||||
@@ -27,13 +27,13 @@ package com.github.games647.fastlogin.core.hooks;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class DefaultPasswordGeneratorTest {
|
||||
|
||||
@Test
|
||||
void smokeTestPassword() {
|
||||
PasswordGenerator<?> passwordGenerator = new DefaultPasswordGenerator<>();
|
||||
assertTrue(passwordGenerator.getRandomPassword(null).length() == 8);
|
||||
assertEquals(8, passwordGenerator.getRandomPassword(null).length());
|
||||
}
|
||||
}
|
||||
|
||||
28
pom.xml
28
pom.xml
@@ -48,9 +48,9 @@
|
||||
<!-- Set default for non-git clones -->
|
||||
<git.commit.id>Unknown</git.commit.id>
|
||||
|
||||
<maven.compiler.release>8</maven.compiler.release>
|
||||
<maven.compiler.release>11</maven.compiler.release>
|
||||
|
||||
<lombook.version>1.18.34</lombook.version>
|
||||
<lombook.version>1.18.32</lombook.version>
|
||||
|
||||
<floodgate.version>2.2.3-SNAPSHOT</floodgate.version>
|
||||
<geyser.version>2.2.1-SNAPSHOT</geyser.version>
|
||||
@@ -83,7 +83,7 @@
|
||||
<plugin>
|
||||
<!-- Update compiler plugin for old Maven versions like GH runner -->
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.14.0</version>
|
||||
<version>3.13.0</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
@@ -106,7 +106,7 @@
|
||||
<plugin>
|
||||
<groupId>com.mycila</groupId>
|
||||
<artifactId>license-maven-plugin</artifactId>
|
||||
<version>5.0.0</version>
|
||||
<version>4.3</version>
|
||||
<configuration>
|
||||
<licenseSets>
|
||||
<licenseSet>
|
||||
@@ -134,7 +134,7 @@
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<version>3.3.1</version>
|
||||
<configuration>
|
||||
<configLocation>checkstyle.xml</configLocation>
|
||||
<consoleOutput>true</consoleOutput>
|
||||
@@ -145,7 +145,7 @@
|
||||
<dependency>
|
||||
<groupId>com.puppycrawl.tools</groupId>
|
||||
<artifactId>checkstyle</artifactId>
|
||||
<version>10.23.0</version>
|
||||
<version>10.16.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<executions>
|
||||
@@ -162,7 +162,7 @@
|
||||
<!-- Require newer versions for Junit5 support -->
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<version>3.2.5</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.2</version>
|
||||
<version>3.4.1</version>
|
||||
<!-- Explicitly enable multi-release for the scheduler,
|
||||
because detection from class shading doesn't work -->
|
||||
<executions>
|
||||
@@ -218,10 +218,18 @@
|
||||
</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.12.2</version>
|
||||
<version>5.10.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -229,7 +237,7 @@
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>5.17.0</version>
|
||||
<version>5.11.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -62,23 +62,9 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<annotationProcessors>
|
||||
<annotationProcessor>
|
||||
com.velocitypowered.api.plugin.ap.PluginAnnotationProcessor
|
||||
</annotationProcessor>
|
||||
</annotationProcessors>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<version>3.5.3</version>
|
||||
<configuration>
|
||||
<minimizeJar>true</minimizeJar>
|
||||
|
||||
@@ -175,15 +161,25 @@
|
||||
<dependency>
|
||||
<groupId>com.velocitypowered</groupId>
|
||||
<artifactId>velocity-api</artifactId>
|
||||
<version>3.4.0-SNAPSHOT</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.moandjiezana.toml</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.spongepowered</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!--Velocity does not ship any database driver-->
|
||||
<dependency>
|
||||
<groupId>org.mariadb.jdbc</groupId>
|
||||
<artifactId>mariadb-java-client</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<version>3.3.3</version>
|
||||
<exclusions>
|
||||
<!-- Exclude JNA implementation for WAFFLE - Windows Authentication Framework (mariadb)-->
|
||||
<exclusion>
|
||||
|
||||
@@ -230,8 +230,4 @@ public class FastLoginVelocity implements PlatformPlugin<CommandSource> {
|
||||
public UUID getProxyId() {
|
||||
return proxyId;
|
||||
}
|
||||
|
||||
public ProxyServer getServer() {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ package com.github.games647.fastlogin.velocity;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.LoginSource;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
@@ -46,17 +47,17 @@ public class VelocityLoginSource implements LoginSource {
|
||||
|
||||
@Override
|
||||
public void enableOnlinemode() {
|
||||
preLoginEvent.setResult(PreLoginEvent.PreLoginComponentResult.forceOnlineMode());
|
||||
preLoginEvent.setResult(PreLoginComponentResult.forceOnlineMode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kick(String message) {
|
||||
if (message == null) {
|
||||
preLoginEvent.setResult(PreLoginEvent.PreLoginComponentResult.denied(
|
||||
preLoginEvent.setResult(PreLoginComponentResult.denied(
|
||||
Component.text("Kicked").color(NamedTextColor.WHITE))
|
||||
);
|
||||
} else {
|
||||
preLoginEvent.setResult(PreLoginEvent.PreLoginComponentResult.denied(
|
||||
preLoginEvent.setResult(PreLoginComponentResult.denied(
|
||||
LegacyComponentSerializer.legacyAmpersand().deserialize(message))
|
||||
);
|
||||
}
|
||||
@@ -66,8 +67,4 @@ public class VelocityLoginSource implements LoginSource {
|
||||
public InetSocketAddress getAddress() {
|
||||
return connection.getRemoteAddress();
|
||||
}
|
||||
|
||||
public InboundConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,12 @@ import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.velocitypowered.api.event.ResultedEvent;
|
||||
import com.velocitypowered.api.event.ResultedEvent.GenericResult;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class VelocityFastLoginAutoLoginEvent
|
||||
implements FastLoginAutoLoginEvent, ResultedEvent<ResultedEvent.GenericResult> {
|
||||
implements FastLoginAutoLoginEvent, ResultedEvent<GenericResult> {
|
||||
|
||||
private final LoginSession session;
|
||||
private final StoredProfile profile;
|
||||
|
||||
@@ -36,29 +36,21 @@ 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;
|
||||
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;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.api.util.GameProfile.Property;
|
||||
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;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -66,8 +58,6 @@ 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;
|
||||
|
||||
@@ -87,15 +77,6 @@ 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:
|
||||
@@ -121,7 +102,7 @@ public class ConnectListener {
|
||||
if (event.isOnlineMode()) {
|
||||
LoginSession session = plugin.getSession().get(event.getConnection().getRemoteAddress());
|
||||
if (session == null) {
|
||||
plugin.getLog().error("No active login session found for onlinemode player {}", event.getUsername());
|
||||
plugin.getLog().warn("No active login session found for player {}", event.getUsername());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -133,7 +114,7 @@ public class ConnectListener {
|
||||
StoredProfile playerProfile = session.getProfile();
|
||||
playerProfile.setId(verifiedUUID);
|
||||
if (!plugin.getCore().getConfig().get("premiumUuid", true)) {
|
||||
UUID offlineUUID = UUIDAdapter.generateOfflineId(event.getUsername());
|
||||
UUID offlineUUID = UUIDAdapter.generateOfflineId(playerProfile.getName());
|
||||
event.setGameProfile(event.getGameProfile().withId(offlineUUID));
|
||||
plugin.getLog().info("Overridden UUID from {} to {} (based of {}) on {}",
|
||||
verifiedUUID, offlineUUID, verifiedUsername, event.getConnection());
|
||||
@@ -146,9 +127,9 @@ public class ConnectListener {
|
||||
}
|
||||
}
|
||||
|
||||
private List<GameProfile.Property> removeSkin(Collection<Property> oldProperties) {
|
||||
List<GameProfile.Property> newProperties = new ArrayList<>(oldProperties.size());
|
||||
for (GameProfile.Property property : oldProperties) {
|
||||
private List<Property> removeSkin(Collection<Property> oldProperties) {
|
||||
List<Property> newProperties = new ArrayList<>(oldProperties.size());
|
||||
for (Property property : oldProperties) {
|
||||
if (!"textures".equals(property.getName())) {
|
||||
newProperties.add(property);
|
||||
}
|
||||
@@ -185,84 +166,6 @@ public class ConnectListener {
|
||||
Runnable loginTask = new ForceLoginTask(plugin.getCore(), player, server, session);
|
||||
|
||||
// Delay at least one second, otherwise the login command can be missed
|
||||
plugin.getScheduler().runAsyncDelayed(loginTask, Duration.ofSeconds(1));
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDisconnect(DisconnectEvent disconnectEvent) {
|
||||
Player player = disconnectEvent.getPlayer();
|
||||
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
|
||||
|
||||
plugin.getSession().remove(player.getRemoteAddress());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
plugin.getScheduler().runAsync(loginTask);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +38,10 @@ import com.google.common.io.ByteStreams;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent.ForwardResult;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -69,7 +69,7 @@ public class PluginMessageListener {
|
||||
|
||||
//the client shouldn't be able to read the messages in order to know something about server internal states
|
||||
//moreover the client shouldn't be able to fake a running premium check by sending the result message
|
||||
pluginMessageEvent.setResult(PluginMessageEvent.ForwardResult.handled());
|
||||
pluginMessageEvent.setResult(ForwardResult.handled());
|
||||
|
||||
if (!(pluginMessageEvent.getSource() instanceof ServerConnection)) {
|
||||
//check if the message is sent from the server
|
||||
@@ -83,12 +83,12 @@ public class PluginMessageListener {
|
||||
plugin.getScheduler().runAsync(() -> readMessage(forPlayer, channel, data));
|
||||
}
|
||||
|
||||
private void readMessage(Player sender, String channel, byte[] data) {
|
||||
private void readMessage(Player forPlayer, String channel, byte[] data) {
|
||||
FastLoginCore<Player, CommandSource, FastLoginVelocity> core = plugin.getCore();
|
||||
|
||||
ByteArrayDataInput dataInput = ByteStreams.newDataInput(data);
|
||||
if (successChannel.equals(channel)) {
|
||||
onSuccessMessage(sender);
|
||||
onSuccessMessage(forPlayer);
|
||||
} else if (changeChannel.equals(channel)) {
|
||||
ChangePremiumMessage changeMessage = new ChangePremiumMessage();
|
||||
changeMessage.readFrom(dataInput);
|
||||
@@ -96,20 +96,10 @@ public class PluginMessageListener {
|
||||
String playerName = changeMessage.getPlayerName();
|
||||
boolean isSourceInvoker = changeMessage.isSourceInvoker();
|
||||
if (changeMessage.shouldEnable()) {
|
||||
Boolean premiumWarning = plugin.getCore().getConfig().get("premium-warning", true);
|
||||
if (playerName.equals(sender.getUsername()) && premiumWarning
|
||||
&& !core.getPendingConfirms().contains(sender.getUniqueId())) {
|
||||
String message = core.getMessage("premium-warning");
|
||||
sender.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(message));
|
||||
core.getPendingConfirms().add(sender.getUniqueId());
|
||||
return;
|
||||
}
|
||||
|
||||
core.getPendingConfirms().remove(sender.getUniqueId());
|
||||
Runnable task = new AsyncToggleMessage(core, sender, playerName, true, isSourceInvoker);
|
||||
Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, true, isSourceInvoker);
|
||||
plugin.getScheduler().runAsync(task);
|
||||
} else {
|
||||
Runnable task = new AsyncToggleMessage(core, sender, playerName, false, isSourceInvoker);
|
||||
Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, false, isSourceInvoker);
|
||||
plugin.getScheduler().runAsync(task);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,8 +82,7 @@ public class AsyncPremiumCheck extends JoinManagement<Player, CommandSource, Vel
|
||||
String username, boolean registered) {
|
||||
source.enableOnlinemode();
|
||||
VelocityLoginSession session = new VelocityLoginSession(username, registered, profile);
|
||||
plugin.getLog().error("Putting session: {}", source.getConnection());
|
||||
plugin.getSession().put(source.getConnection().getRemoteAddress(), session);
|
||||
plugin.getSession().put(source.getAddress(), session);
|
||||
|
||||
String ip = source.getAddress().getAddress().getHostAddress();
|
||||
plugin.getCore().addLoginAttempt(ip, username);
|
||||
@@ -92,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.getConnection().getRemoteAddress(), session);
|
||||
plugin.getSession().put(source.getAddress(), session);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,26 +33,29 @@ 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 Player sender;
|
||||
private final CommandSource sender;
|
||||
private final String senderName;
|
||||
private final String targetPlayer;
|
||||
private final boolean toPremium;
|
||||
private final boolean isPlayerSender;
|
||||
|
||||
public AsyncToggleMessage(FastLoginCore<Player, CommandSource, FastLoginVelocity> core,
|
||||
Player sender, String playerName, boolean toPremium, boolean playerSender) {
|
||||
CommandSource sender, String playerName, boolean toPremium, boolean playerSender) {
|
||||
this.core = core;
|
||||
this.sender = sender;
|
||||
this.targetPlayer = playerName;
|
||||
this.toPremium = toPremium;
|
||||
this.isPlayerSender = playerSender;
|
||||
this.senderName = sender.getUsername();
|
||||
if (sender instanceof Player playSender) {
|
||||
senderName = playSender.getUsername();
|
||||
} else {
|
||||
senderName = "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -79,14 +82,7 @@ public class AsyncToggleMessage implements Runnable {
|
||||
? PremiumToggleReason.COMMAND_OTHER : PremiumToggleReason.COMMAND_SELF;
|
||||
core.getPlugin().getProxy().getEventManager().fire(
|
||||
new VelocityFastLoginPremiumToggleEvent(playerProfile, reason));
|
||||
|
||||
if (isPlayerSender && core.getConfig().getBoolean("kick-toggle", true)) {
|
||||
TextComponent msg = LegacyComponentSerializer.legacyAmpersand()
|
||||
.deserialize(core.getMessage("remove-premium"));
|
||||
sender.disconnect(msg);
|
||||
} else {
|
||||
sendMessage("remove-premium");
|
||||
}
|
||||
sendMessage("remove-premium");
|
||||
}
|
||||
|
||||
private void activatePremium() {
|
||||
|
||||
@@ -55,8 +55,8 @@ public class FloodgateAuthTask
|
||||
core.getPlugin().getSession().put(player.getRemoteAddress(), session);
|
||||
|
||||
// enable auto login based on the value of 'autoLoginFloodgate' in config.yml
|
||||
boolean forcedOnlineMode = autoLoginFloodgate.equals("true")
|
||||
|| (autoLoginFloodgate.equals("linked") && isLinked);
|
||||
boolean forcedOnlineMode = "true".equals(autoLoginFloodgate)
|
||||
|| ("linked".equals(autoLoginFloodgate) && isLinked);
|
||||
|
||||
// delay sending force command, because Paper will process the login event asynchronously
|
||||
// In this case it means that the force command (plugin message) is already received and processed while
|
||||
|
||||
Reference in New Issue
Block a user