Compare commits

..

1 Commits

98 changed files with 1050 additions and 3527 deletions

View File

@@ -1,14 +1,6 @@
---
name: Bug report
about: Something isn't working
title: ''
labels: 'bug'
assignees: ''
---
[//]: # (Lines in this format are considered as comments and will not be displayed.)
[//]: # (Before reporting make sure you're running the **latest build** of the plugin and checked for existing issues!)
[//]: #
[//]: # (Before reporting make sure you're running the **latest build** of the plugin and checked for duplicate issues!)
### What behaviour is observed:
[//]: # (What happened?)
@@ -19,23 +11,18 @@ assignees: ''
### Steps/models to reproduce:
[//]: # (The actions that cause the issue. Please explain it in detail)
### Screenshots (if applicable)
[//]: # (You can drop the files here directly)
### Plugin list:
[//]: # (This can be found by running `/pl`)
### Environment description
[//]: # (Server software with exact version number, Minecraft version, SQLite/MySQL/MariaDB, ...)
[//]: # (Server software with exact version number, Minecraft version, SQLite/MySQL, ...)
### Plugin version or build number (don't write latest):
[//]: # (This can be found by running `/version plugin-name`.)
### Server Log:
[//]: # (No images please - only the textual representation)
[Hastebin](https://hastebin.com/) / [Gist](https://gist.github.com/) link of the error, stacktrace or the complete log (if any)
### Error Log:
[Hastebin](https://hastebin.com/) / [Gist](https://gist.github.com/) link of the error or stacktrace (if any)
### Configuration:
[//]: # (No images please - only the textual representation)
[//]: # (remember to delete any sensitive data)
[Hastebin](https://hastebin.com/) / [Gist](https://gist.github.com/) link of your config.yml file

View File

@@ -1,22 +0,0 @@
---
name: Enhancement request
about: New feature or change request
title: ''
labels: 'enhancement'
assignees: ''
---
[//]: # (Lines in this format are considered as comments and will not be displayed.)
### Is your feature request related to a problem? Please describe.
[//]: # (A clear and concise description of what the problem is. Ex. I'm always frustrated when [...])
### Describe the solution you'd like
[//]: # (A clear and concise description of what you want to happen.)
### Describe alternatives you've considered
[//]: # (A clear and concise description of any alternative solutions or features you've considered.)
### Additional context
[//]: # (Add any other context or screenshots about the feature request here.)

View File

@@ -1,10 +0,0 @@
---
name: Question
about: You want to ask something
title: ''
labels: 'question'
assignees: ''
---

View File

@@ -1,8 +0,0 @@
[//]: # (Lines in this format are considered as comments and will not be displayed.)
[//]: # (If your work is in progress, please consider making a draft pull request.)
### Summary of your change
[//]: # (Example: motiviation, enhancement)
### Related issue
[//]: # (Reference it using '#NUMBER'. Ex: Fixes/Related #...)

View File

@@ -1,49 +0,0 @@
# Automatically build, run unit and integration tests to detect errors early (CI provided by GitHub)
# including making pull requests review easier
# Human readable name in the actions tab
name: Java CI
# Build on every push and pull request regardless of the branch
# Wiki: https://help.github.com/en/actions/reference/events-that-trigger-workflows
on:
- push
- pull_request
jobs:
# job id
build_and_test:
# Environment image - always newest OS
runs-on: ubuntu-latest
# Run steps
steps:
# Pull changes
- uses: actions/checkout@v2.3.4
# Cache artifacts - however this has the downside that we don't get notified of
# artifact resolution failures like invalid repository
# Nevertheless the repositories should be more stable and it makes no sense to pull
# a same version every time
# A dry run would make more sense
- uses: actions/cache@v2.1.4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
# Setup Java
- name: Set up JDK
uses: actions/setup-java@v1.4.3
with:
# Use Java 11, because it's minimum required version
java-version: 11
# Build and test (included in package)
- name: Build with Maven and test
# Run non-interactive, package (with compile+test),
# ignore snapshot updates, because they are likely to have breaking changes, enforce checksums to validate
# possible errors in dependencies
run: mvn package test --batch-mode --no-snapshot-updates --strict-checksums --file pom.xml

3
.gitignore vendored
View File

@@ -15,7 +15,6 @@ nb-configuration.xml
# Maven
target/
pom.xml.versionsBackup
# Gradle
.gradle
@@ -42,5 +41,3 @@ hs_err_pid*
# Mac filesystem dust
.DS_Store
# Factorypath from Visual Studio Code
.factorypath

13
.travis.yml Normal file
View File

@@ -0,0 +1,13 @@
# Use https://travis-ci.org/ for automatic testing
# speed up testing https://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/
sudo: false
# This is a java project
language: java
script: mvn test -B
jdk:
- oraclejdk8
- oraclejdk9

View File

@@ -1,7 +1,5 @@
### 1.11
* TODO: Replace reflection with methodhandles
* Use direct proxies instead of ssl factories for multiple IP-addresses
* Remove local address check for multiple IP-addresses
* Fix parsing of local IP-addresses

View File

@@ -3,7 +3,7 @@
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).
## Features
### Features:
* Detect paid accounts from others
* Automatically login paid accounts (premium)
@@ -11,114 +11,73 @@ So they don't need to enter passwords. This is also called auto login (auto-logi
* Cauldron support
* Forge/Sponge message support
* Premium UUID support
* Forward skins
* Forwards Skins
* Detect user name changed and will update the existing database record
* BungeeCord support
* Auto register new premium players
* Plugin: ProtocolSupport is supported and can be used as an alternative to ProtocolLib
* No client modifications needed
* Good performance by using async operations
* Good performance by using async non blocking operations
* Locale messages
* Support for Bedrock players proxied through FloodGate
## Issues
Please use issues for bug reports, suggestions, questions and more. Please check for existing issues. Existing issues
can be voted up by adding up vote to the original post. Closing issues means that they are marked as resolved. Comments
are still allowed and it could be re-opened.
## Development builds
Development builds of this project can be acquired at the provided CI (continuous integration) server. It contains the
latest changes from the Source-Code in preparation for the following release. This means they could contain new
features, bug fixes and other changes since the last release.
They **could** contain new bugs and are likely to be less stable than released versions.
Specific builds can be grabbed by clicking on the build number on the left side or by clicking on status to retrieve the
latest build.
https://ci.codemc.org/job/Games647/job/FastLogin/changes
* Import the database from similar plugins
* Free
* Open source
***
## Commands
### Commands:
/premium [player] Label the invoker or the argument as paid account
/cracked [player] Label the invoker or the argument as cracked account
## Permissions
### Permissions:
fastlogin.bukkit.command.premium
fastlogin.bukkit.command.cracked
fastlogin.command.premium.other
fastlogin.command.cracked.other
fastlogin.command.import
## Placeholder
This plugin supports `PlaceholderAPI` on `Spigot`. It exports the following variable
`%fastlogin_status%`. In BungeeCord environments, the status of a player will be delivered with a delay after the player
already successful joined the server. This takes about a couple of milliseconds. In this case the value
will be `Unknown`.
Possible values: `Premium`, `Cracked`, `Unknown`
## Requirements
* Plugin:
* [ProtocolLib](https://www.spigotmc.org/resources/protocollib.1997/) or
* [ProtocolSupport](https://www.spigotmc.org/resources/protocolsupport.7201/)
* [Spigot](https://www.spigotmc.org) 1.8.8+
### Requirements:
* Plugin: [ProtocolLib](https://www.spigotmc.org/resources/protocollib.1997/) or
[ProtocolSupport](https://www.spigotmc.org/resources/protocolsupport.7201/)
* [Spigot](https://www.spigotmc.org) 1.7+
* Java 8+
* Run Spigot (or a fork e.g. Paper) and/or BungeeCord (or a fork e.g. Waterfall) in offline mode
* An auth plugin.
* Run Spigot and/or BungeeCord/Waterfall in offline mode (see server.properties or config.yml)
* An auth plugin. Supported plugins
### Supported auth plugins
#### Bukkit/Spigot/Paper
#### Spigot/Paper
* [AdvancedLogin (Paid)](https://www.spigotmc.org/resources/advancedlogin.10510/)
* [AuthMe (5.X)](https://dev.bukkit.org/bukkit-plugins/authme-reloaded/)
* [xAuth](https://dev.bukkit.org/bukkit-plugins/xauth/)
* [LogIt](https://github.com/games647/LogIt)
* [AdvancedLogin (Paid)](https://www.spigotmc.org/resources/advancedlogin.10510/)
* [CrazyLogin](https://dev.bukkit.org/bukkit-plugins/crazylogin/)
* [LoginSecurity](https://dev.bukkit.org/bukkit-plugins/loginsecurity/)
* [LogIt](https://github.com/games647/LogIt)
* [SodionAuth (2.0+)](https://github.com/Mohist-Community/SodionAuth)
* [UltraAuth](https://dev.bukkit.org/bukkit-plugins/ultraauth-aa/)
* [UserLogin](https://www.spigotmc.org/resources/userlogin.80669/)
* [xAuth](https://dev.bukkit.org/bukkit-plugins/xauth/)
#### BungeeCord/Waterfall
* [BungeeAuth](https://www.spigotmc.org/resources/bungeeauth.493/)
* [BungeeAuthenticator](https://www.spigotmc.org/resources/bungeecordauthenticator.87669/)
## Network requests
This plugin performs network requests to:
* https://api.mojang.com - retrieving uuid data to decide if we should activate premium login
* https://sessionserver.mojang.com - verify if the player is the owner of that account
***
## How to install
### How to install
### Spigot/Paper
#### Bukkit/Spigot/Paper
1. Download and install ProtocolLib/ProtocolSupport
2. Download and install FastLogin (or FastLoginBukkit for newer versions)
1. Download and install ProtocolLib
2. Download and install FastLogin
3. Set your server in offline mode by setting the value onlinemode in your server.properties to false
### BungeeCord/Waterfall
#### BungeeCord/Waterfall
1. Activate BungeeCord in the Spigot configuration
2. Restart your server
3. Now there is `allowed-proxies.txt` file in the FastLogin folder
3. Now there is proxy-whitelist file in the FastLogin folder
Put your stats id from the BungeeCord config into this file
4. Activate ipForward in your BungeeCord config
5. Download and Install FastLogin (or FastLoginBungee in newer versions) on BungeeCord AND Spigot
(on the servers where your login plugin is or where player should be able to execute the commands of FastLogin)
5. Download and Install FastLogin on BungeeCord AND Spigot (on the servers where your login plugin is)
6. Check your database settings in the config of FastLogin on BungeeCord
7. Set proxy and Spigot in offline mode by setting the value onlinemode in your config.yml to false
7. Set your proxy (BungeeCord) in offline mode by setting the value onlinemode in your config.yml to false
8. You should *always* firewall your Spigot server that it's only accessible through BungeeCord
* https://www.spigotmc.org/wiki/bungeecord-installation/#post-installation
* BungeeCord doesn't support SQLite per default, so you should change the configuration to MySQL or MariaDB

View File

@@ -1,11 +1,11 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.games647</groupId>
<artifactId>fastlogin</artifactId>
<version>1.11-SNAPSHOT</version>
<version>1.11</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -20,7 +20,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<version>3.1.0</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<shadedArtifactAttached>false</shadedArtifactAttached>
@@ -41,10 +41,6 @@
<pattern>com.google.gson</pattern>
<shadedPattern>fastlogin.gson</shadedPattern>
</relocation>
<relocation>
<pattern>io.papermc.lib</pattern>
<shadedPattern>fastlogin.paperlib</shadedPattern>
</relocation>
</relocations>
</configuration>
<executions>
@@ -60,92 +56,78 @@
</build>
<repositories>
<!-- PaperSpigot API and PaperLib -->
<!--Bukkit-Server-API -->
<repository>
<id>papermc</id>
<url>https://papermc.io/repo/repository/maven-public/</url>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<!-- ProtocolLib -->
<!--LoginSecurity-->
<repository>
<id>lenis0012-repo</id>
<url>http://ci.lenis0012.com/plugin/repository/everything/</url>
</repository>
<!--ProtocolLib-->
<repository>
<id>dmulloy2-repo</id>
<url>https://repo.dmulloy2.net/nexus/repository/public/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
<url>http://repo.dmulloy2.net/content/groups/public/</url>
</repository>
<!-- AuthMe Reloaded, xAuth and LoginSecurity -->
<!--AuthMe Reloaded and xAuth -->
<repository>
<id>codemc-releases</id>
<url>https://repo.codemc.io/repository/maven-public/</url>
<id>codemc-repo</id>
<url>https://repo.codemc.org/repository/maven-public/</url>
</repository>
<!-- GitHub automatic maven builds -->
<!--GitHub automatic maven builds-->
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<!-- PlaceholderAPI -->
<!--PlaceholderAPI -->
<repository>
<id>placeholderapi</id>
<url>https://repo.extendedclip.com/content/repositories/placeholderapi</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
<url>http://repo.extendedclip.com/content/repositories/placeholderapi/</url>
</repository>
</repositories>
<dependencies>
<!--Common plugin component-->
<dependency>
<groupId>com.github.games647</groupId>
<artifactId>fastlogin.core</artifactId>
<version>${project.version}</version>
</dependency>
<!-- PaperSpigot API for correcting usercache usage -->
<!--Server API-->
<dependency>
<groupId>com.destroystokyo.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.15.2-R0.1-SNAPSHOT</version>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.12.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- PaperLib for checking if server uses PaperSpigot -->
<dependency>
<groupId>io.papermc</groupId>
<artifactId>paperlib</artifactId>
<version>1.0.6</version>
<scope>compile</scope>
</dependency>
<!--Library for listening and sending Minecraft packets-->
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>4.6.0</version>
<version>4.3.0</version>
<scope>provided</scope>
</dependency>
<!--Changing onlinemode on login process-->
<dependency>
<groupId>com.github.ProtocolSupport</groupId>
<artifactId>ProtocolSupport</artifactId>
<!--4.29.dev after commit about API improvements-->
<version>3a80c661fe</version>
<!--4.25.dev-->
<version>a4f060dc46</version>
<scope>provided</scope>
</dependency>
<!--Provide premium placeholders-->
<dependency>
<groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId>
<version>2.10.8</version>
<version>2.8.4</version>
<scope>provided</scope>
<optional>true</optional>
<exclusions>
@@ -174,7 +156,7 @@
<dependency>
<groupId>com.lenis0012.bukkit</groupId>
<artifactId>loginsecurity</artifactId>
<version>3.0.2</version>
<version>2.1.7</version>
<scope>provided</scope>
<optional>true</optional>
<exclusions>
@@ -241,19 +223,5 @@
<scope>system</scope>
<systemPath>${project.basedir}/lib/UltraAuth v2.1.2.jar</systemPath>
</dependency>
<dependency>
<groupId>com.github.Mohist-Community.SodionAuth</groupId>
<artifactId>SodionAuth-Bukkit</artifactId>
<version>b74392aa34</version>
<exclusions>
<exclusion>
<groupId>com.github.Mohist-Community.SodionAuth</groupId>
<artifactId>SodionAuth-Libs</artifactId>
</exclusion>
</exclusions>
<optional>true</optional>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,8 +1,8 @@
package com.github.games647.fastlogin.bukkit.auth;
package com.github.games647.fastlogin.bukkit;
import com.github.games647.craftapi.model.skin.SkinProperty;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.LoginSession;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.shared.LoginSession;
import java.util.Optional;
@@ -13,47 +13,21 @@ import java.util.Optional;
*/
public class BukkitLoginSession extends LoginSession {
private static final byte[] EMPTY_ARRAY = {};
private final String serverId;
private final byte[] verifyToken;
private SkinProperty skinProperty;
private boolean verified;
private SkinProperty skinProperty;
public BukkitLoginSession(String username, String serverId, byte[] verifyToken, boolean registered
, StoredProfile profile) {
public BukkitLoginSession(String username, boolean registered, StoredProfile profile) {
super(username, registered, profile);
this.serverId = serverId;
this.verifyToken = verifyToken.clone();
}
// available for proxies
//available for BungeeCord
public BukkitLoginSession(String username, boolean registered) {
this(username, "", EMPTY_ARRAY, registered, null);
this(username, registered, null);
}
//cracked player
public BukkitLoginSession(String username, StoredProfile profile) {
this(username, "", EMPTY_ARRAY, false, profile);
}
//ProtocolSupport
public BukkitLoginSession(String username, boolean registered, StoredProfile profile) {
this(username, "", EMPTY_ARRAY, registered, profile);
}
/**
* Gets the verify token the server sent to the client.
*
* Empty if it's a proxy connection
*
* @return the verify token from the server
*/
public synchronized byte[] getVerifyToken() {
return verifyToken.clone();
this(username, false, profile);
}
/**

View File

@@ -1,27 +0,0 @@
package com.github.games647.fastlogin.bukkit;
import com.github.games647.fastlogin.core.AsyncScheduler;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadFactory;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.slf4j.Logger;
public class BukkitScheduler extends AsyncScheduler {
private final Plugin plugin;
private final Executor syncExecutor;
public BukkitScheduler(Plugin plugin, Logger logger, ThreadFactory threadFactory) {
super(logger, threadFactory);
this.plugin = plugin;
syncExecutor = r -> Bukkit.getScheduler().runTask(plugin, r);
}
public Executor getSyncExecutor() {
return syncExecutor;
}
}

View File

@@ -1,24 +0,0 @@
package com.github.games647.fastlogin.bukkit;
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
import com.github.games647.fastlogin.core.SessionManager;
import java.net.InetSocketAddress;
import java.util.UUID;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
public class BukkitSessionManager extends SessionManager<PlayerQuitEvent, InetSocketAddress, BukkitLoginSession>
implements Listener {
@EventHandler
@Override
public void onPlayQuit(PlayerQuitEvent quitEvent) {
Player player = quitEvent.getPlayer();
UUID playerId = player.getUniqueId();
endPlaySession(playerId);
}
}

View File

@@ -1,31 +1,30 @@
package com.github.games647.fastlogin.bukkit;
import com.github.games647.fastlogin.bukkit.auth.proxy.ProxyManager;
import com.github.games647.fastlogin.bukkit.auth.protocollib.ProtocolLibListener;
import com.github.games647.fastlogin.bukkit.auth.protocolsupport.ProtocolSupportListener;
import com.github.games647.fastlogin.bukkit.command.CrackedCommand;
import com.github.games647.fastlogin.bukkit.command.PremiumCommand;
import com.github.games647.fastlogin.bukkit.hook.DelayedAuthHook;
import com.github.games647.fastlogin.bukkit.commands.CrackedCommand;
import com.github.games647.fastlogin.bukkit.commands.PremiumCommand;
import com.github.games647.fastlogin.bukkit.listener.BungeeListener;
import com.github.games647.fastlogin.bukkit.listener.ConnectionListener;
import com.github.games647.fastlogin.bukkit.listener.PaperPreLoginListener;
import com.github.games647.fastlogin.bukkit.listener.protocolsupport.ProtocolSupportListener;
import com.github.games647.fastlogin.bukkit.tasks.DelayedAuthHook;
import com.github.games647.fastlogin.core.CommonUtil;
import com.github.games647.fastlogin.core.PremiumStatus;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.messages.ChannelMessage;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.shared.PlatformPlugin;
import io.papermc.lib.PaperLib;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.messaging.PluginMessageRecipient;
import org.slf4j.Logger;
/**
@@ -33,49 +32,49 @@ import org.slf4j.Logger;
*/
public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<CommandSender> {
private final BukkitSessionManager sessionManager = new BukkitSessionManager();
//1 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang)
private final ConcurrentMap<String, BukkitLoginSession> loginSession = new ConcurrentHashMap<>();
private final Logger logger = CommonUtil.createLoggerFromJDK(getLogger());
private final Map<UUID, PremiumStatus> premiumPlayers = new ConcurrentHashMap<>();
private final FastLoginCore<Player, CommandSender, FastLoginBukkit> core = new FastLoginCore<>(this);
private final Logger logger;
private final BukkitScheduler scheduler;
private ProxyManager proxyManager;
private PremiumPlaceholder premiumPlaceholder;
public FastLoginBukkit() {
this.logger = CommonUtil.createLoggerFromJDK(getLogger());
this.scheduler = new BukkitScheduler(this, logger, getThreadFactory());
}
private boolean serverStarted;
private boolean bungeeCord;
private FastLoginCore<Player, CommandSender, FastLoginBukkit> core;
@Override
public void onEnable() {
core = new FastLoginCore<>(this);
core.load();
try {
bungeeCord = Class.forName("org.spigotmc.SpigotConfig").getDeclaredField("bungee").getBoolean(null);
} catch (ClassNotFoundException notFoundEx) {
//ignore server has no bungee support
} catch (Exception ex) {
logger.warn("Cannot check bungeecord support. You use a non-Spigot build", ex);
}
if (getServer().getOnlineMode()) {
//we need to require offline to prevent a loginSession request for a offline player
logger.error("Server has to be in offline mode");
logger.error("Server have to be in offline mode");
setEnabled(false);
return;
}
proxyManager = new ProxyManager(this);
proxyManager.initialize();
PluginManager pluginManager = getServer().getPluginManager();
if (!proxyManager.isEnabled()) {
if (bungeeCord) {
//check for incoming messages from the bungeecord version of this plugin
getServer().getMessenger().registerIncomingPluginChannel(this, getName(), new BungeeListener(this));
getServer().getMessenger().registerOutgoingPluginChannel(this, getName());
} else {
if (!core.setupDatabase()) {
setEnabled(false);
return;
}
if (pluginManager.isPluginEnabled("ProtocolSupport")) {
pluginManager.registerEvents(new ProtocolSupportListener(this, core.getRateLimiter()), this);
} else if (pluginManager.isPluginEnabled("ProtocolLib")) {
ProtocolLibListener.register(this, core.getRateLimiter());
pluginManager.registerEvents(new ProtocolSupportListener(this), this);
} else {
logger.warn("Either ProtocolLib or ProtocolSupport have to be installed if you don't use proxies");
logger.warn("ProtocolSupport have to be installed if you don't use BungeeCord");
}
}
@@ -84,70 +83,72 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
pluginManager.registerEvents(new ConnectionListener(this), this);
//if server is using paper - we need to add one more listener to correct the usercache usage
if (PaperLib.isPaper()) {
pluginManager.registerEvents(new PaperPreLoginListener(this), this);
}
//register commands using a unique name
getCommand("premium").setExecutor(new PremiumCommand(this));
getCommand("cracked").setExecutor(new CrackedCommand(this));
if (pluginManager.isPluginEnabled("PlaceholderAPI")) {
premiumPlaceholder = new PremiumPlaceholder(this);
premiumPlaceholder.register();
//prevents NoClassDef errors if it's not available
PremiumPlaceholder.register(this);
}
}
@Override
public void onDisable() {
loginSession.clear();
premiumPlayers.clear();
core.close();
proxyManager.cleanup();
if (getServer().getPluginManager().isPluginEnabled("PlaceholderAPI") && premiumPlaceholder != null) {
premiumPlaceholder.unregister();
if (core != null) {
core.close();
}
//remove old blacklists
getServer().getOnlinePlayers().forEach(player -> player.removeMetadata(getName(), this));
}
public FastLoginCore<Player, CommandSender, FastLoginBukkit> getCore() {
return core;
}
/**
* Fetches the premium status of an online player.
*
* @param onlinePlayer
* @return the online status or unknown if an error happened, the player isn't online or a proxy doesn't send
* us the status message yet (This means you cannot check the login status on the PlayerJoinEvent).
* @deprecated this method could be removed in future versions and exists only as a temporarily solution
*/
@Deprecated
public PremiumStatus getStatus(UUID onlinePlayer) {
StoredProfile playSession = sessionManager.getPlaySession(onlinePlayer);
return Optional.ofNullable(playSession).map(profile -> {
if (profile.isPremium())
return PremiumStatus.PREMIUM;
return PremiumStatus.CRACKED;
}).orElse(PremiumStatus.UNKNOWN);
}
/**
* Gets a thread-safe map about players which are connecting to the server are being checked to be premium (paid
* account)
*
* @return a thread-safe loginSession map
*/
public BukkitSessionManager getSessionManager() {
return sessionManager;
public ConcurrentMap<String, BukkitLoginSession> getLoginSessions() {
return loginSession;
}
public Map<UUID, PremiumStatus> getPremiumPlayers() {
return premiumPlayers;
}
public ProxyManager getProxyManager() {
return proxyManager;
public boolean isBungeeEnabled() {
return bungeeCord;
}
/**
* Fetches the premium status of an online player.
*
* @param onlinePlayer
* @return the online status or unknown if an error happened, the player isn't online or BungeeCord doesn't send
* us the status message yet (This means you cannot check the login status on the PlayerJoinEvent).
* @deprecated this method could be removed in future versions and exists only as a temporarily solution
*/
@Deprecated
public PremiumStatus getStatus(UUID onlinePlayer) {
return premiumPlayers.getOrDefault(onlinePlayer, PremiumStatus.UNKNOWN);
}
public void sendPluginMessage(PluginMessageRecipient player, ChannelMessage message) {
if (player != null) {
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
dataOutput.writeUTF(message.getChannelName());
message.writeTo(dataOutput);
player.sendPluginMessage(this, this.getName(), dataOutput.toByteArray());
}
}
@Override
@@ -160,11 +161,6 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
return logger;
}
@Override
public BukkitScheduler getScheduler() {
return scheduler;
}
@Override
public void sendMessage(CommandSender receiver, String message) {
receiver.sendMessage(message);

View File

@@ -1,83 +0,0 @@
package com.github.games647.fastlogin.bukkit;
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginAutoLoginEvent;
import com.github.games647.fastlogin.core.PremiumStatus;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.message.SuccessMessage;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.auth.ForceLoginManagement;
import com.github.games647.fastlogin.core.auth.LoginSession;
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
import java.util.concurrent.ExecutionException;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.metadata.FixedMetadataValue;
public class ForceLoginTask extends ForceLoginManagement<Player, CommandSender, BukkitLoginSession, FastLoginBukkit> {
public ForceLoginTask(FastLoginCore<Player, CommandSender, FastLoginBukkit> core, Player player,
BukkitLoginSession session) {
super(core, player, session);
}
@Override
public void run() {
// block this target player for proxy ID brute force attacks
FastLoginBukkit plugin = core.getPlugin();
player.setMetadata(core.getPlugin().getName(), new FixedMetadataValue(plugin, true));
if (session != null && !session.getUsername().equals(player.getName())) {
String playerName = player.getName();
plugin.getLog().warn("Player username {} is not matching session {}", playerName, session.getUsername());
return;
}
super.run();
PremiumStatus status = PremiumStatus.CRACKED;
if (isOnlineMode()) {
status = PremiumStatus.PREMIUM;
}
plugin.getPremiumPlayers().put(player.getUniqueId(), status);
}
@Override
public FastLoginAutoLoginEvent callFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile) {
BukkitFastLoginAutoLoginEvent event = new BukkitFastLoginAutoLoginEvent(session, profile);
core.getPlugin().getServer().getPluginManager().callEvent(event);
return event;
}
@Override
public void onForceActionSuccess(LoginSession session) {
if (core.getPlugin().getProxyManager().isEnabled()) {
core.getPlugin().getProxyManager().sendPluginMessage(player, new SuccessMessage());
}
}
@Override
public String getName(Player player) {
return player.getName();
}
@Override
public boolean isOnline(Player player) {
try {
//the player-list isn't thread-safe
return Bukkit.getScheduler().callSyncMethod(core.getPlugin(), player::isOnline).get();
} catch (InterruptedException | ExecutionException ex) {
core.getPlugin().getLog().error("Failed to perform thread-safe online check for {}", player, ex);
return false;
}
}
@Override
public boolean isOnlineMode() {
return session.isVerified();
}
}

View File

@@ -1,12 +1,11 @@
package com.github.games647.fastlogin.bukkit;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.PlaceholderHook;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
public class PremiumPlaceholder extends PlaceholderExpansion {
private static final String PLACEHOLDER_VARIABLE = "status";
public class PremiumPlaceholder extends PlaceholderHook {
private final FastLoginBukkit plugin;
@@ -15,32 +14,15 @@ public class PremiumPlaceholder extends PlaceholderExpansion {
}
@Override
public String onRequest(OfflinePlayer player, String identifier) {
// player is null if offline
if (player != null && PLACEHOLDER_VARIABLE.equals(identifier)) {
return plugin.getStatus(player.getUniqueId()).getReadableName();
public String onPlaceholderRequest(Player player, String variable) {
if (player != null && "fastlogin_status".contains(variable)) {
return plugin.getStatus(player.getUniqueId()).name();
}
return null;
return "";
}
@Override
public String getIdentifier() {
return plugin.getName();
}
@Override
public String getRequiredPlugin() {
return plugin.getName();
}
@Override
public String getAuthor() {
return String.join(", ", plugin.getDescription().getAuthors());
}
@Override
public String getVersion() {
return plugin.getDescription().getVersion();
public static void register(FastLoginBukkit plugin) {
PlaceholderAPI.registerPlaceholderHook(plugin, new PremiumPlaceholder(plugin));
}
}

View File

@@ -1,131 +0,0 @@
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* Encryption and decryption minecraft util for connection between servers
* and paid Minecraft account clients.
*
* @see net.minecraft.server.MinecraftEncryption
*/
public class EncryptionUtil {
public static final int VERIFY_TOKEN_LENGTH = 4;
public static final String KEY_PAIR_ALGORITHM = "RSA";
private EncryptionUtil() {
// utility
}
/**
* Generate a RSA key pair
*
* @return The RSA key pair.
*/
public static KeyPair generateKeyPair() {
// KeyPair b()
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGORITHM);
keyPairGenerator.initialize(1_024);
return keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException nosuchalgorithmexception) {
// Should be existing in every vm
throw new ExceptionInInitializerError(nosuchalgorithmexception);
}
}
/**
* Generate a random token. This is used to verify that we are communicating with the same player
* in a login session.
*
* @param random random generator
* @return an error with 4 bytes long
*/
public static byte[] generateVerifyToken(Random random) {
// extracted from LoginListener
byte[] token = new byte[VERIFY_TOKEN_LENGTH];
random.nextBytes(token);
return token;
}
/**
* Generate the server id based on client and server data.
*
* @param sessionId session for the current login attempt
* @param sharedSecret shared secret between the client and the server
* @param publicKey public key of the server
* @return the server id formatted as a hexadecimal string.
*/
public static String getServerIdHashString(String sessionId, SecretKey sharedSecret, PublicKey publicKey) {
// found in LoginListener
try {
byte[] serverHash = getServerIdHash(sessionId, publicKey, sharedSecret);
return (new BigInteger(serverHash)).toString(16);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
/**
* Decrypts the content and extracts the key spec.
*
* @param privateKey private server key
* @param sharedKey the encrypted shared key
* @return shared secret key
* @throws GeneralSecurityException if it fails to decrypt the data
*/
public static SecretKey decryptSharedKey(PrivateKey privateKey, byte[] sharedKey) throws GeneralSecurityException {
// SecretKey a(PrivateKey var0, byte[] var1)
return new SecretKeySpec(decrypt(privateKey, sharedKey), "AES");
}
public static byte[] decrypt(PrivateKey key, byte[] data) throws GeneralSecurityException {
// b(Key var0, byte[] var1)
Cipher cipher = Cipher.getInstance(key.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, key);
return decrypt(cipher, data);
}
/**
* Decrypted the given data using the cipher.
*
* @param cipher decryption cypher initialized with the private key
* @param data the encrypted data
* @return clear text data
* @throws GeneralSecurityException if it fails to decrypt the data
*/
private static byte[] decrypt(Cipher cipher, byte[] data) throws GeneralSecurityException {
// inlined: byte[] a(int var0, Key var1, byte[] var2), Cipher a(int var0, String var1, Key
// var2)
return cipher.doFinal(data);
}
private static byte[] getServerIdHash(
String sessionId, PublicKey publicKey, SecretKey sharedSecret)
throws NoSuchAlgorithmException {
// byte[] a(String var0, PublicKey var1, SecretKey var2)
MessageDigest digest = MessageDigest.getInstance("SHA-1");
// inlined from byte[] a(String var0, byte[]... var1)
digest.update(sessionId.getBytes(StandardCharsets.ISO_8859_1));
digest.update(sharedSecret.getEncoded());
digest.update(publicKey.getEncoded());
return digest.digest();
}
}

View File

@@ -1,23 +0,0 @@
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerLoginEvent.Result;
public class InitializedListener implements Listener {
private final ProtocolLibListener module;
protected InitializedListener(ProtocolLibListener protocolLibModule) {
this.module = protocolLibModule;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
if (loginEvent.getResult() == Result.ALLOWED && !module.isReadyToInject()) {
loginEvent.disallow(Result.KICK_OTHER, module.getPlugin().getCore().getMessage("not-started"));
}
}
}

View File

@@ -1,89 +0,0 @@
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.auth.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.JoinManagement;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import java.security.PublicKey;
import java.util.Random;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class NameCheckTask extends JoinManagement<Player, CommandSender, ProtocolLibLoginSource>
implements Runnable {
private final FastLoginBukkit plugin;
private final PacketEvent packetEvent;
private final PublicKey publicKey;
private final Random random;
private final Player player;
private final String username;
protected NameCheckTask(FastLoginBukkit plugin, PacketEvent packetEvent, Random random,
Player player, String username, PublicKey publicKey) {
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
this.plugin = plugin;
this.packetEvent = packetEvent;
this.publicKey = publicKey;
this.random = random;
this.player = player;
this.username = username;
}
@Override
public void run() {
try {
super.onLogin(username, new ProtocolLibLoginSource(packetEvent, player, random, publicKey));
} finally {
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
}
}
@Override
public FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, ProtocolLibLoginSource source, StoredProfile profile) {
BukkitFastLoginPreLoginEvent event = new BukkitFastLoginPreLoginEvent(username, source, profile);
plugin.getServer().getPluginManager().callEvent(event);
return event;
}
//Minecraft server implementation
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
@Override
public void requestPremiumLogin(ProtocolLibLoginSource source, StoredProfile profile
, String username, boolean registered) {
try {
source.setOnlineMode();
} catch (Exception ex) {
plugin.getLog().error("Cannot send encryption packet. Falling back to cracked login for: {}", profile, ex);
return;
}
String ip = player.getAddress().getAddress().getHostAddress();
core.getPendingLogin().put(ip + username, new Object());
String serverId = source.getServerId();
byte[] verify = source.getVerifyToken();
BukkitLoginSession playerSession = new BukkitLoginSession(username, serverId, verify, registered, profile);
plugin.getSessionManager().startLoginSession(player.getAddress(), playerSession);
//cancel only if the player has a paid account otherwise login as normal offline player
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
packetEvent.setCancelled(true);
}
}
@Override
public void startCrackedSession(ProtocolLibLoginSource source, StoredProfile profile, String username) {
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
plugin.getSessionManager().startLoginSession(player.getAddress(), loginSession);
}
}

View File

@@ -1,124 +0,0 @@
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.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.auth.RateLimiter;
import io.papermc.lib.PaperLib;
import java.security.KeyPair;
import java.security.SecureRandom;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import static com.comphenix.protocol.PacketType.Login.Client.ENCRYPTION_BEGIN;
import static com.comphenix.protocol.PacketType.Login.Client.START;
public class ProtocolLibListener extends PacketAdapter {
private final FastLoginBukkit plugin;
//just create a new once on plugin enable. This used for verify token generation
private final SecureRandom random = new SecureRandom();
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
private final RateLimiter rateLimiter;
// Wait before the server is fully started. This is workaround, because connections right on startup are not
// injected by ProtocolLib
private boolean serverStarted;
protected ProtocolLibListener(FastLoginBukkit plugin, RateLimiter rateLimiter) {
//run async in order to not block the server, because we are making api calls to Mojang
super(params()
.plugin(plugin)
.types(START, ENCRYPTION_BEGIN)
.optionAsync());
this.plugin = plugin;
this.rateLimiter = rateLimiter;
}
public static void register(FastLoginBukkit plugin, RateLimiter rateLimiter) {
//they will be created with a static builder, because otherwise it will throw a NoClassDefFoundError
ProtocolLibListener packetListener = new ProtocolLibListener(plugin, rateLimiter);
ProtocolLibrary.getProtocolManager()
.getAsynchronousManager()
.registerAsyncHandler(packetListener)
.start();
PluginManager pluginManager = Bukkit.getServer().getPluginManager();
pluginManager.registerEvents(new InitializedListener(packetListener), plugin);
//if server is using paper - we need to set the skin at pre login anyway, so no need for this listener
if (!PaperLib.isPaper() && plugin.getConfig().getBoolean("forwardSkin")) {
pluginManager.registerEvents(new SkinApplyListener(plugin), plugin);
}
}
@Override
public void onPacketReceiving(PacketEvent packetEvent) {
if (packetEvent.isCancelled() || plugin.getCore().getAuthPluginHook() == null) {
return;
}
markReadyToInject();
Player sender = packetEvent.getPlayer();
PacketType packetType = packetEvent.getPacketType();
if (packetType == START) {
if (!rateLimiter.tryAcquire()) {
plugin.getLog().warn("Join limit hit - Ignoring player {}", sender);
return;
}
onLogin(packetEvent, sender);
} else {
onEncryptionBegin(packetEvent, sender);
}
}
private void onEncryptionBegin(PacketEvent packetEvent, Player sender) {
byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0);
packetEvent.getAsyncMarker().incrementProcessingDelay();
Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, sharedSecret, keyPair);
plugin.getScheduler().runAsync(verifyTask);
}
private void onLogin(PacketEvent packetEvent, Player player) {
//this includes ip:port. Should be unique for an incoming login request with a timeout of 2 minutes
String sessionKey = player.getAddress().toString();
//remove old data every time on a new login in order to keep the session only for one person
plugin.getSessionManager().endLoginSession(player.getAddress());
//player.getName() won't work at this state
PacketContainer packet = packetEvent.getPacket();
String username = packet.getGameProfiles().read(0).getName();
plugin.getLog().trace("GameProfile {} with {} connecting", sessionKey, username);
packetEvent.getAsyncMarker().incrementProcessingDelay();
Runnable nameCheckTask = new NameCheckTask(plugin, packetEvent, random, player, username, keyPair.getPublic());
plugin.getScheduler().runAsync(nameCheckTask);
}
public void markReadyToInject() {
this.serverStarted = true;
}
public boolean isReadyToInject() {
return serverStarted;
}
@Override
public FastLoginBukkit getPlugin() {
return plugin;
}
}

View File

@@ -1,108 +0,0 @@
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.events.PacketEvent;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.github.games647.fastlogin.core.auth.LoginSource;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.Random;
import org.bukkit.entity.Player;
import static com.comphenix.protocol.PacketType.Login.Server.DISCONNECT;
import static com.comphenix.protocol.PacketType.Login.Server.ENCRYPTION_BEGIN;
class ProtocolLibLoginSource implements LoginSource {
private final PacketEvent packetEvent;
private final Player player;
private final Random random;
private final PublicKey publicKey;
private final String serverId = "";
private byte[] verifyToken;
protected ProtocolLibLoginSource(PacketEvent packetEvent, Player player, Random random, PublicKey publicKey) {
this.packetEvent = packetEvent;
this.player = player;
this.random = random;
this.publicKey = publicKey;
}
@Override
public void setOnlineMode() throws Exception {
verifyToken = EncryptionUtil.generateVerifyToken(random);
/*
* Packet Information: https://wiki.vg/Protocol#Encryption_Request
*
* ServerID="" (String) key=public server key verifyToken=random 4 byte array
*/
PacketContainer newPacket = new PacketContainer(ENCRYPTION_BEGIN);
newPacket.getStrings().write(0, serverId);
StructureModifier<PublicKey> keyModifier = newPacket.getSpecificModifier(PublicKey.class);
int verifyField = 0;
if (keyModifier.getFields().isEmpty()) {
// Since 1.16.4 this is now a byte field
newPacket.getByteArrays().write(0, publicKey.getEncoded());
verifyField++;
} else {
keyModifier.write(0, publicKey);
}
newPacket.getByteArrays().write(verifyField, verifyToken);
//serverId is a empty string
ProtocolLibrary.getProtocolManager().sendServerPacket(player, newPacket);
}
@Override
public void kick(String message) throws InvocationTargetException {
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
PacketContainer kickPacket = new PacketContainer(DISCONNECT);
kickPacket.getChatComponents().write(0, WrappedChatComponent.fromText(message));
try {
//send kick packet at login state
//the normal event.getPlayer.kickPlayer(String) method does only work at play state
protocolManager.sendServerPacket(player, kickPacket);
} finally {
//tell the server that we want to close the connection
player.kickPlayer("Disconnect");
}
}
@Override
public InetSocketAddress getAddress() {
return packetEvent.getPlayer().getAddress();
}
public String getServerId() {
return serverId;
}
public byte[] getVerifyToken() {
return verifyToken.clone();
}
@Override
public String toString() {
return this.getClass().getSimpleName() + '{' +
"packetEvent=" + packetEvent +
", player=" + player +
", random=" + random +
", serverId='" + serverId + '\'' +
", verifyToken=" + Arrays.toString(verifyToken) +
'}';
}
}

View File

@@ -1,52 +0,0 @@
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
import com.github.games647.craftapi.model.skin.Textures;
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
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.event.player.PlayerLoginEvent.Result;
public class SkinApplyListener implements Listener {
private static final Class<?> GAME_PROFILE = MinecraftReflection.getGameProfileClass();
private static final MethodAccessor GET_PROPERTIES = Accessors.getMethodAccessor(GAME_PROFILE, "getProperties");
private final FastLoginBukkit plugin;
protected SkinApplyListener(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.LOW)
//run this on the loginEvent to let skins plugins see the skin like in normal Minecraft behaviour
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
if (loginEvent.getResult() != Result.ALLOWED) {
return;
}
Player player = loginEvent.getPlayer();
//go through every session, because player.getAddress is null
//loginEvent.getAddress is just a InetAddress not InetSocketAddress, so not unique enough
BukkitLoginSession session = plugin.getSessionManager().getLoginSession(player.getAddress());
if (session.getUsername().equals(player.getName())) {
session.getSkin().ifPresent(skin -> applySkin(player, skin.getValue(), skin.getSignature()));
}
}
private void applySkin(Player player, String skinData, String signature) {
WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player);
WrappedSignedProperty skin = WrappedSignedProperty.fromValues(Textures.KEY, skinData, signature);
gameProfile.getProperties().put(Textures.KEY, skin);
}
}

View File

@@ -1,264 +0,0 @@
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.server.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.github.games647.craftapi.model.auth.Verification;
import com.github.games647.craftapi.model.skin.SkinProperty;
import com.github.games647.craftapi.resolver.MojangResolver;
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import org.bukkit.entity.Player;
import static com.comphenix.protocol.PacketType.Login.Client.START;
import static com.comphenix.protocol.PacketType.Login.Server.DISCONNECT;
public class VerifyResponseTask implements Runnable {
private final FastLoginBukkit plugin;
private final PacketEvent packetEvent;
private final KeyPair serverKey;
private final Player player;
private final byte[] sharedSecret;
private static Method encryptMethod;
private static Method cipherMethod;
protected VerifyResponseTask(FastLoginBukkit plugin, PacketEvent packetEvent, Player player,
byte[] sharedSecret, KeyPair keyPair) {
this.plugin = plugin;
this.packetEvent = packetEvent;
this.player = player;
this.sharedSecret = Arrays.copyOf(sharedSecret, sharedSecret.length);
this.serverKey = keyPair;
}
@Override
public void run() {
try {
BukkitLoginSession session = plugin.getSessionManager().getLoginSession(player.getAddress());
if (session == null) {
disconnect("invalid-request", true
, "GameProfile {0} tried to send encryption response at invalid state", player.getAddress());
} else {
verifyResponse(session);
}
} finally {
//this is a fake packet; it shouldn't be send to the server
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
packetEvent.setCancelled(true);
}
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
}
}
private void verifyResponse(BukkitLoginSession session) {
PrivateKey privateKey = serverKey.getPrivate();
SecretKey loginKey;
try {
loginKey = EncryptionUtil.decryptSharedKey(privateKey, sharedSecret);
} catch (GeneralSecurityException securityEx) {
disconnect("error-kick", false, "Cannot decrypt received contents", securityEx);
return;
}
try {
if (!checkVerifyToken(session) || !enableEncryption(loginKey)) {
return;
}
} catch (Exception ex) {
disconnect("error-kick", false, "Cannot decrypt received contents", ex);
return;
}
String serverId = EncryptionUtil.getServerIdHashString("", loginKey, serverKey.getPublic());
String requestedUsername = session.getRequestUsername();
InetSocketAddress socketAddress = player.getAddress();
try {
MojangResolver resolver = plugin.getCore().getResolver();
InetAddress address = socketAddress.getAddress();
Optional<Verification> response = resolver.hasJoined(requestedUsername, serverId, address);
if (response.isPresent()) {
Verification verification = response.get();
plugin.getLog().info("Profile {} has a verified premium account: {}", requestedUsername, verification);
String realUsername = verification.getName();
if (realUsername == null) {
disconnect("invalid-session", true, "Username field null for {}", requestedUsername);
return;
}
SkinProperty[] properties = verification.getProperties();
if (properties.length > 0) {
session.setSkinProperty(properties[0]);
}
session.setVerifiedUsername(realUsername);
session.setUuid(verification.getId());
session.setVerified(true);
setPremiumUUID(session.getUuid());
receiveFakeStartPacket(realUsername);
} else {
//user tried to fake a authentication
disconnect("invalid-session", true
, "GameProfile {0} ({1}) tried to log in with an invalid session ServerId: {2}"
, session.getRequestUsername(), socketAddress, serverId);
}
} catch (IOException ioEx) {
disconnect("error-kick", false, "Failed to connect to session server", ioEx);
}
}
private void setPremiumUUID(UUID premiumUUID) {
if (plugin.getConfig().getBoolean("premiumUuid") && premiumUUID != null) {
try {
Object networkManager = getNetworkManager();
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/NetworkManager.java#L69
FieldUtils.writeField(networkManager, "spoofedUUID", premiumUUID, true);
} catch (Exception exc) {
plugin.getLog().error("Error setting premium uuid of {}", player, exc);
}
}
}
private boolean checkVerifyToken(BukkitLoginSession session) throws GeneralSecurityException {
byte[] requestVerify = session.getVerifyToken();
//encrypted verify token
byte[] responseVerify = packetEvent.getPacket().getByteArrays().read(1);
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L182
if (!Arrays.equals(requestVerify, EncryptionUtil.decrypt(serverKey.getPrivate(), responseVerify))) {
//check if the verify token are equal to the server sent one
disconnect("invalid-verify-token", true
, "GameProfile {0} ({1}) tried to login with an invalid verify token. Server: {2} Client: {3}"
, session.getRequestUsername(), packetEvent.getPlayer().getAddress(), requestVerify, responseVerify);
return false;
}
return true;
}
//try to get the networkManager from ProtocolLib
private Object getNetworkManager() throws IllegalAccessException, ClassNotFoundException {
Object injectorContainer = TemporaryPlayerFactory.getInjectorFromPlayer(player);
//ChannelInjector
Class<?> injectorClass = Class.forName("com.comphenix.protocol.injector.netty.Injector");
Object rawInjector = FuzzyReflection.getFieldValue(injectorContainer, injectorClass, true);
return FieldUtils.readField(rawInjector, "networkManager", true);
}
private boolean enableEncryption(SecretKey loginKey) throws IllegalArgumentException {
// Initialize method reflections
if (encryptMethod == null) {
Class<?> networkManagerClass = MinecraftReflection.getNetworkManagerClass();
try {
// Try to get the old (pre MC 1.16.4) encryption method
encryptMethod = FuzzyReflection.fromClass(networkManagerClass)
.getMethodByParameters("a", SecretKey.class);
} catch (IllegalArgumentException exception) {
// Get the new encryption method
encryptMethod = FuzzyReflection.fromClass(networkManagerClass)
.getMethodByParameters("a", Cipher.class, Cipher.class);
// Get the needed Cipher helper method (used to generate ciphers from login key)
Class<?> encryptionClass = MinecraftReflection.getMinecraftClass("MinecraftEncryption");
cipherMethod = FuzzyReflection.fromClass(encryptionClass)
.getMethodByParameters("a", int.class, Key.class);
}
}
try {
Object networkManager = this.getNetworkManager();
// If cipherMethod is null - use old encryption (pre MC 1.16.4), otherwise use the new cipher one
if (cipherMethod == null) {
// Encrypt/decrypt packet flow, this behaviour is expected by the client
encryptMethod.invoke(networkManager, loginKey);
} else {
// Create ciphers from login key
Object decryptionCipher = cipherMethod.invoke(null, Cipher.DECRYPT_MODE, loginKey);
Object encryptionCipher = cipherMethod.invoke(null, Cipher.ENCRYPT_MODE, loginKey);
// Encrypt/decrypt packet flow, this behaviour is expected by the client
encryptMethod.invoke(networkManager, decryptionCipher, encryptionCipher);
}
} catch (Exception ex) {
disconnect("error-kick", false, "Couldn't enable encryption", ex);
return false;
}
return true;
}
private void disconnect(String reasonKey, boolean debug, String logMessage, Object... arguments) {
if (debug) {
plugin.getLog().debug(logMessage, arguments);
} else {
plugin.getLog().error(logMessage, arguments);
}
kickPlayer(plugin.getCore().getMessage(reasonKey));
}
private void kickPlayer(String reason) {
PacketContainer kickPacket = new PacketContainer(DISCONNECT);
kickPacket.getChatComponents().write(0, WrappedChatComponent.fromText(reason));
try {
//send kick packet at login state
//the normal event.getPlayer.kickPlayer(String) method does only work at play state
ProtocolLibrary.getProtocolManager().sendServerPacket(player, kickPacket);
//tell the server that we want to close the connection
player.kickPlayer("Disconnect");
} catch (InvocationTargetException ex) {
plugin.getLog().error("Error sending kick packet for: {}", player, ex);
}
}
//fake a new login packet in order to let the server handle all the other stuff
private void receiveFakeStartPacket(String username) {
//see StartPacketListener for packet information
PacketContainer startPacket = new PacketContainer(START);
//uuid is ignored by the packet definition
WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username);
startPacket.getGameProfiles().write(0, fakeProfile);
try {
//we don't want to handle our own packets so ignore filters
ProtocolLibrary.getProtocolManager().recieveClientPacket(player, startPacket, false);
} catch (InvocationTargetException | IllegalAccessException ex) {
plugin.getLog().warn("Failed to fake a new start packet for: {}", username, ex);
//cancel the event in order to prevent the server receiving an invalid packet
kickPlayer(plugin.getCore().getMessage("error-kick"));
}
}
}

View File

@@ -1,102 +0,0 @@
package com.github.games647.fastlogin.bukkit.auth.protocolsupport;
import com.github.games647.craftapi.UUIDAdapter;
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
import com.github.games647.fastlogin.core.auth.RateLimiter;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.JoinManagement;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import java.net.InetSocketAddress;
import java.util.Optional;
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;
public class ProtocolSupportListener extends JoinManagement<Player, CommandSender, ProtocolLoginSource>
implements Listener {
private final FastLoginBukkit plugin;
private final RateLimiter rateLimiter;
public ProtocolSupportListener(FastLoginBukkit plugin, RateLimiter rateLimiter) {
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
this.plugin = plugin;
this.rateLimiter = rateLimiter;
}
@EventHandler
public void onLoginStart(PlayerLoginStartEvent loginStartEvent) {
if (loginStartEvent.isLoginDenied() || plugin.getCore().getAuthPluginHook() == null) {
return;
}
if (!rateLimiter.tryAcquire()) {
plugin.getLog().warn("Join limit hit - Ignoring player {}", loginStartEvent.getConnection());
return;
}
String username = loginStartEvent.getConnection().getProfile().getName();
InetSocketAddress address = loginStartEvent.getAddress();
//remove old data every time on a new login in order to keep the session only for one person
plugin.getSessionManager().endLoginSession(address);
super.onLogin(username, new ProtocolLoginSource(loginStartEvent));
}
@EventHandler
public void onConnectionClosed(ConnectionCloseEvent closeEvent) {
InetSocketAddress address = closeEvent.getConnection().getAddress();
plugin.getSessionManager().endLoginSession(address);
}
@EventHandler
public void onPropertiesResolve(PlayerProfileCompleteEvent profileCompleteEvent) {
InetSocketAddress address = profileCompleteEvent.getAddress();
BukkitLoginSession session = plugin.getSessionManager().getLoginSession(address);
if (session != null && profileCompleteEvent.getConnection().getProfile().isOnlineMode()) {
session.setVerified(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.setOnlineMode();
String ip = source.getAddress().getAddress().getHostAddress();
plugin.getCore().getPendingLogin().put(ip + username, new Object());
BukkitLoginSession playerSession = new BukkitLoginSession(username, registered, profile);
plugin.getSessionManager().startLoginSession(source.getAddress(), playerSession);
}
@Override
public void startCrackedSession(ProtocolLoginSource source, StoredProfile profile, String username) {
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
plugin.getSessionManager().startLoginSession(source.getAddress(), loginSession);
}
}

View File

@@ -1,169 +0,0 @@
package com.github.games647.fastlogin.bukkit.auth.proxy;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
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 java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Stream;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageRecipient;
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 ProxyManager {
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 proxy support is disabled
private Set<UUID> proxyIds;
private final FastLoginBukkit plugin;
private boolean enabled;
private final Set<UUID> firedJoinEvents = new HashSet<>();
public ProxyManager(FastLoginBukkit plugin) {
this.plugin = plugin;
}
public void cleanup() {
//remove old blocked status
Bukkit.getOnlinePlayers().forEach(player -> player.removeMetadata(plugin.getName(), plugin));
}
public void sendPluginMessage(PluginMessageRecipient player, ChannelMessage message) {
if (player != null) {
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
message.writeTo(dataOutput);
NamespaceKey channel = new NamespaceKey(plugin.getName(), message.getChannelName());
player.sendPluginMessage(plugin, channel.getCombinedName(), dataOutput.toByteArray());
}
}
public boolean isEnabled() {
return enabled;
}
public void initialize() {
try {
enabled = detectProxy();
} catch (Exception ex) {
plugin.getLog().warn("Cannot check proxy support. Fallback to non-proxy mode", ex);
}
if (enabled) {
proxyIds = loadProxyIds();
registerPluginChannels();
}
}
private boolean detectProxy() throws Exception {
try {
enabled = Class.forName("org.spigotmc.SpigotConfig").getDeclaredField("bungee").getBoolean(null);
return enabled;
} catch (ClassNotFoundException notFoundEx) {
//ignore server has no proxy support
return false;
} catch (Exception ex) {
throw ex;
}
}
private void registerPluginChannels() {
Server server = Bukkit.getServer();
// check for incoming messages from the proxy version of this plugin
String groupId = plugin.getName();
String forceChannel = NamespaceKey.getCombined(groupId, LoginActionMessage.FORCE_CHANNEL);
server.getMessenger().registerIncomingPluginChannel(plugin, forceChannel, new ProxyMessagingListener(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 Set<UUID> loadProxyIds() {
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 proxy 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
*/
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
* proxy messages after the PlayerJoinEvent fires including the delay.
*
* 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
* @return event fired including delay
*/
public synchronized boolean didJoinEventFired(Player player) {
return firedJoinEvents.contains(player.getUniqueId());
}
/**
* Player quit clean up
*
* @param player
*/
public synchronized void cleanup(Player player) {
firedJoinEvents.remove(player.getUniqueId());
}
}

View File

@@ -1,114 +0,0 @@
package com.github.games647.fastlogin.bukkit.auth.proxy;
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.ForceLoginTask;
import com.github.games647.fastlogin.core.PremiumStatus;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import com.github.games647.fastlogin.core.message.LoginActionMessage;
import com.github.games647.fastlogin.core.message.LoginActionMessage.Type;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import java.net.InetSocketAddress;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener;
/**
* Responsible for receiving messages from a proxy instance.
*
* This class also receives the plugin message from the proxy version of this plugin in order to get notified if
* the connection is in online mode.
*/
public class ProxyMessagingListener implements PluginMessageListener {
private final FastLoginBukkit plugin;
protected ProxyMessagingListener(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
ByteArrayDataInput dataInput = ByteStreams.newDataInput(message);
LoginActionMessage loginMessage = new LoginActionMessage();
loginMessage.readFrom(dataInput);
plugin.getLog().debug("Received plugin message {}", loginMessage);
Player targetPlayer = player;
if (!loginMessage.getPlayerName().equals(player.getName())) {
targetPlayer = Bukkit.getPlayerExact(loginMessage.getPlayerName());;
}
if (targetPlayer == null) {
plugin.getLog().warn("Force action player {} not found", loginMessage.getPlayerName());
return;
}
// fail if target player is blocked because already authenticated or wrong proxy id
if (targetPlayer.hasMetadata(plugin.getName())) {
plugin.getLog().warn("Received message {} from a blocked player {}", loginMessage, targetPlayer);
} else {
UUID sourceId = loginMessage.getProxyId();
if (plugin.getProxyManager().isProxyAllowed(sourceId)) {
readMessage(targetPlayer, loginMessage);
} else {
plugin.getLog().warn("Received proxy id: {} that doesn't exist in the proxy file", sourceId);
}
}
}
private void readMessage(Player player, LoginActionMessage message) {
String playerName = message.getPlayerName();
Type type = message.getType();
InetSocketAddress address = player.getAddress();
plugin.getLog().info("Player info {} command for {} from proxy", type, playerName);
if (type == Type.LOGIN) {
onLoginMessage(player, playerName, address);
} else if (type == Type.REGISTER) {
onRegisterMessage(player, playerName, address);
} else if (type == Type.CRACKED) {
//we don't start a force login task here so update it manually
plugin.getPremiumPlayers().put(player.getUniqueId(), PremiumStatus.CRACKED);
}
}
private void onLoginMessage(Player player, String playerName, InetSocketAddress address) {
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, true);
startLoginTaskIfReady(player, playerSession);
}
private void onRegisterMessage(Player player, String playerName, InetSocketAddress address) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
AuthPlugin<Player> authPlugin = plugin.getCore().getAuthPluginHook();
try {
//we need to check if the player is registered on Bukkit too
if (authPlugin == null || !authPlugin.isRegistered(playerName)) {
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, false);
startLoginTaskIfReady(player, playerSession);
}
} catch (Exception ex) {
plugin.getLog().error("Failed to query isRegistered for player: {}", player, ex);
}
});
}
private void startLoginTaskIfReady(Player player, BukkitLoginSession session) {
session.setVerified(true);
plugin.getSessionManager().startLoginSession(player.getAddress(), session);
// only start a new login task if the join event fired earlier. This event then didn
boolean result = plugin.getProxyManager().didJoinEventFired(player);
plugin.getLog().info("Delaying force login until join event fired?: {}", result);
if (result) {
Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player, session);
Bukkit.getScheduler().runTaskAsynchronously(plugin, forceLoginTask);
}
}
}

View File

@@ -1,14 +1,12 @@
package com.github.games647.fastlogin.bukkit.command;
package com.github.games647.fastlogin.bukkit.commands;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPremiumToggleEvent;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.StoredProfile;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import static com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent.*;
public class CrackedCommand extends ToggleCommand {
public CrackedCommand(FastLoginBukkit plugin) {
@@ -35,8 +33,8 @@ public class CrackedCommand extends ToggleCommand {
return;
}
if (plugin.getProxyManager().isEnabled()) {
sendProxyActivateMessage(sender, sender.getName(), false);
if (plugin.isBungeeEnabled()) {
sendBungeeActivateMessage(sender, sender.getName(), false);
plugin.getCore().sendLocaleMessage("wait-on-proxy", sender);
} else {
//todo: load async if
@@ -46,10 +44,8 @@ public class CrackedCommand extends ToggleCommand {
profile.setPremium(false);
profile.setId(null);
plugin.getScheduler().runAsync(() -> {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
plugin.getCore().getStorage().save(profile);
plugin.getServer().getPluginManager().callEvent(
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_OTHER));
});
} else {
plugin.getCore().sendLocaleMessage("not-premium", sender);
@@ -80,15 +76,13 @@ public class CrackedCommand extends ToggleCommand {
plugin.getCore().sendLocaleMessage("remove-premium", sender);
profile.setPremium(false);
plugin.getScheduler().runAsync(() -> {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
plugin.getCore().getStorage().save(profile);
plugin.getServer().getPluginManager().callEvent(
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_OTHER));
});
}
}
private boolean forwardCrackedCommand(CommandSender sender, String target) {
return forwardProxyCommand(sender, target, false);
return forwardBungeeCommand(sender, target, false);
}
}

View File

@@ -1,12 +1,11 @@
package com.github.games647.fastlogin.bukkit.command;
package com.github.games647.fastlogin.bukkit.commands;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPremiumToggleEvent;
import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent.PremiumToggleReason;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.StoredProfile;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@@ -14,7 +13,7 @@ import org.bukkit.entity.Player;
/**
* Let users activate fast login by command. This only be accessible if
* the user has access to it's account. So we can make sure that not another
* person with a paid account and the same username can steal their account.
* person with a paid account and the same username can steal his account.
*/
public class PremiumCommand extends ToggleCommand {
@@ -42,29 +41,27 @@ public class PremiumCommand extends ToggleCommand {
return;
}
UUID id = ((Player) sender).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;
}
UUID id = ((Player) sender).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);
//todo: load async
StoredProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
if (profile.isPremium()) {
plugin.getCore().sendLocaleMessage("already-exists", sender);
} else {
//todo: resolve uuid
profile.setPremium(true);
plugin.getScheduler().runAsync(() -> {
plugin.getCore().getStorage().save(profile);
plugin.getServer().getPluginManager().callEvent(
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_SELF));
});
plugin.getCore().getPendingConfirms().remove(id);
//todo: load async
StoredProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
if (profile.isPremium()) {
plugin.getCore().sendLocaleMessage("already-exists", sender);
} else {
//todo: resolve uuid
profile.setPremium(true);
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
plugin.getCore().getStorage().save(profile);
});
plugin.getCore().sendLocaleMessage("add-premium", sender);
}
plugin.getCore().sendLocaleMessage("add-premium", sender);
}
}
private void onPremiumOther(CommandSender sender, Command command, String[] args) {
@@ -88,10 +85,8 @@ public class PremiumCommand extends ToggleCommand {
} else {
//todo: resolve uuid
profile.setPremium(true);
plugin.getScheduler().runAsync(() -> {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
plugin.getCore().getStorage().save(profile);
plugin.getServer().getPluginManager().callEvent(
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_OTHER));
});
plugin.getCore().sendLocaleMessage("add-premium-other", sender);
@@ -99,6 +94,6 @@ public class PremiumCommand extends ToggleCommand {
}
private boolean forwardPremiumCommand(CommandSender sender, String target) {
return forwardProxyCommand(sender, target, true);
return forwardBungeeCommand(sender, target, true);
}
}

View File

@@ -1,8 +1,8 @@
package com.github.games647.fastlogin.bukkit.command;
package com.github.games647.fastlogin.bukkit.commands;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
import com.github.games647.fastlogin.core.message.ChannelMessage;
import com.github.games647.fastlogin.core.messages.ChangePremiumMessage;
import com.github.games647.fastlogin.core.messages.ChannelMessage;
import java.util.Optional;
@@ -30,9 +30,9 @@ public abstract class ToggleCommand implements CommandExecutor {
return true;
}
protected boolean forwardProxyCommand(CommandSender sender, String target, boolean activate) {
if (plugin.getProxyManager().isEnabled()) {
sendProxyActivateMessage(sender, target, activate);
protected boolean forwardBungeeCommand(CommandSender sender, String target, boolean activate) {
if (plugin.isBungeeEnabled()) {
sendBungeeActivateMessage(sender, target, activate);
plugin.getCore().sendLocaleMessage("wait-on-proxy", sender);
return true;
}
@@ -50,10 +50,10 @@ public abstract class ToggleCommand implements CommandExecutor {
return true;
}
protected void sendProxyActivateMessage(CommandSender invoker, String target, boolean activate) {
protected void sendBungeeActivateMessage(CommandSender invoker, String target, boolean activate) {
if (invoker instanceof PluginMessageRecipient) {
ChannelMessage message = new ChangePremiumMessage(target, activate, true);
plugin.getProxyManager().sendPluginMessage((PluginMessageRecipient) invoker, message);
plugin.sendPluginMessage((PluginMessageRecipient) invoker, message);
} else {
Optional<? extends Player> optPlayer = Bukkit.getServer().getOnlinePlayers().stream().findFirst();
if (!optPlayer.isPresent()) {
@@ -63,7 +63,7 @@ public abstract class ToggleCommand implements CommandExecutor {
Player sender = optPlayer.get();
ChannelMessage message = new ChangePremiumMessage(target, activate, false);
plugin.getProxyManager().sendPluginMessage(sender, message);
plugin.sendPluginMessage(sender, message);
}
}
}

View File

@@ -1,52 +0,0 @@
package com.github.games647.fastlogin.bukkit.event;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.LoginSession;
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class BukkitFastLoginAutoLoginEvent extends Event implements FastLoginAutoLoginEvent, Cancellable {
private static final HandlerList handlers = new HandlerList();
private final LoginSession session;
private final StoredProfile profile;
private boolean cancelled;
public BukkitFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile) {
super(true);
this.session = session;
this.profile = profile;
}
@Override
public LoginSession getSession() {
return session;
}
@Override
public StoredProfile getProfile() {
return profile;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -1,47 +0,0 @@
package com.github.games647.fastlogin.bukkit.event;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.LoginSource;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class BukkitFastLoginPreLoginEvent extends Event implements FastLoginPreLoginEvent {
private static final HandlerList handlers = new HandlerList();
private final String username;
private final LoginSource source;
private final StoredProfile profile;
public BukkitFastLoginPreLoginEvent(String username, LoginSource source, StoredProfile profile) {
super(true);
this.username = username;
this.source = source;
this.profile = profile;
}
@Override
public String getUsername() {
return username;
}
@Override
public LoginSource getSource() {
return source;
}
@Override
public StoredProfile getProfile() {
return profile;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -1,38 +0,0 @@
package com.github.games647.fastlogin.bukkit.event;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class BukkitFastLoginPremiumToggleEvent extends Event implements FastLoginPremiumToggleEvent {
private static final HandlerList handlers = new HandlerList();
private final StoredProfile profile;
private final PremiumToggleReason reason;
public BukkitFastLoginPremiumToggleEvent(StoredProfile profile, PremiumToggleReason reason) {
super(true);
this.profile = profile;
this.reason = reason;
}
@Override
public StoredProfile getProfile() {
return profile;
}
@Override
public PremiumToggleReason getReason() {
return reason;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -1,91 +0,0 @@
package com.github.games647.fastlogin.bukkit.hook;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import fr.xephi.authme.api.v3.AuthMeApi;
import fr.xephi.authme.events.RestoreSessionEvent;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.process.register.executors.ApiPasswordRegisterParams;
import fr.xephi.authme.process.register.executors.RegistrationMethod;
import java.lang.reflect.Field;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
/**
* GitHub: https://github.com/Xephi/AuthMeReloaded/
* <p>
* Project page:
* <p>
* Bukkit: https://dev.bukkit.org/bukkit-plugins/authme-reloaded/
* <p>
* Spigot: https://www.spigotmc.org/resources/authme-reloaded.6269/
*/
public class AuthMeHook implements AuthPlugin<Player>, Listener {
private final FastLoginBukkit plugin;
private final AuthMeApi authmeAPI;
private Management authmeManagement;
protected AuthMeHook(FastLoginBukkit plugin) {
this.plugin = plugin;
this.authmeAPI = AuthMeApi.getInstance();
if (plugin.getConfig().getBoolean("respectIpLimit", false)) {
try {
Field managementField = this.authmeAPI.getClass().getDeclaredField("management");
managementField.setAccessible(true);
this.authmeManagement = (Management) managementField.get(this.authmeAPI);
} catch (NoSuchFieldException | IllegalAccessException exception) {
this.authmeManagement = null;
}
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onSessionRestore(RestoreSessionEvent restoreSessionEvent) {
Player player = restoreSessionEvent.getPlayer();
StoredProfile session = plugin.getSessionManager().getPlaySession(player.getUniqueId());
if (session != null && session.isPremium()) {
restoreSessionEvent.setCancelled(true);
}
}
@Override
public boolean forceLogin(Player player) {
if (authmeAPI.isAuthenticated(player)) {
plugin.getLog().warn(ALREADY_AUTHENTICATED, player);
return false;
}
//skips registration and login
authmeAPI.forceLogin(player);
return true;
}
@Override
public boolean isRegistered(String playerName) {
return authmeAPI.isRegistered(playerName);
}
@Override
//this automatically login the player too
public boolean forceRegister(Player player, String password) {
//if we have the management - we can trigger register with IP limit checks
if (authmeManagement != null) {
authmeManagement.performRegister(RegistrationMethod.PASSWORD_REGISTRATION,
ApiPasswordRegisterParams.of(player, password, true));
} else {
authmeAPI.forceRegister(player, password);
}
return true;
}
}

View File

@@ -1,53 +0,0 @@
package com.github.games647.fastlogin.bukkit.hook;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import org.bukkit.entity.Player;
import red.mohist.sodionauth.bukkit.implementation.BukkitPlayer;
import red.mohist.sodionauth.core.SodionAuthApi;
import red.mohist.sodionauth.core.exception.AuthenticatedException;
/**
* GitHub: https://github.com/Mohist-Community/SodionAuth
* <p>
* Project page:
* <p>
* Bukkit: Unknown
* <p>
* Spigot: Unknown
*/
public class SodionAuthHook implements AuthPlugin<Player> {
private final FastLoginBukkit plugin;
protected SodionAuthHook(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@Override
public boolean forceLogin(Player player) {
try {
SodionAuthApi.login(new BukkitPlayer(player));
} catch (AuthenticatedException e) {
plugin.getLog().warn(ALREADY_AUTHENTICATED, player);
return false;
}
return true;
}
@Override
public boolean forceRegister(Player player, String password) {
try{
return SodionAuthApi.register(new BukkitPlayer(player), password);
} catch (UnsupportedOperationException e){
plugin.getLog().warn("Currently SodionAuth is not accepting forceRegister, " +
"It may be caused by unsupported AuthBackend");
return false;
}
}
@Override
public boolean isRegistered(String playerName) {
return SodionAuthApi.isRegistered(playerName);
}
}

View File

@@ -0,0 +1,65 @@
package com.github.games647.fastlogin.bukkit.hooks;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import fr.xephi.authme.api.v3.AuthMeApi;
import fr.xephi.authme.events.RestoreSessionEvent;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
/**
* GitHub: https://github.com/Xephi/AuthMeReloaded/
* <p>
* Project page:
* <p>
* Bukkit: https://dev.bukkit.org/bukkit-plugins/authme-reloaded/
* <p>
* Spigot: https://www.spigotmc.org/resources/authme-reloaded.6269/
*/
public class AuthMeHook implements AuthPlugin<Player>, Listener {
private final FastLoginBukkit plugin;
public AuthMeHook(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onSessionRestore(RestoreSessionEvent restoreSessionEvent) {
Player player = restoreSessionEvent.getPlayer();
String id = '/' + player.getAddress().getAddress().getHostAddress() + ':' + player.getAddress().getPort();
BukkitLoginSession session = plugin.getLoginSessions().get(id);
if (session != null && session.isVerified()) {
restoreSessionEvent.setCancelled(true);
}
}
@Override
public boolean forceLogin(Player player) {
//skips registration and login
if (AuthMeApi.getInstance().isAuthenticated(player)) {
return false;
}
AuthMeApi.getInstance().forceLogin(player);
return true;
}
@Override
public boolean isRegistered(String playerName) {
return AuthMeApi.getInstance().isRegistered(playerName);
}
@Override
public boolean forceRegister(Player player, String password) {
//this automatically registers the player too
AuthMeApi.getInstance().forceRegister(player, password);
return true;
}
}

View File

@@ -1,6 +1,5 @@
package com.github.games647.fastlogin.bukkit.hook;
package com.github.games647.fastlogin.bukkit.hooks;
import com.comphenix.protocol.reflect.FieldUtils;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
@@ -14,6 +13,7 @@ import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.apache.commons.lang.reflect.FieldUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@@ -31,7 +31,7 @@ public class CrazyLoginHook implements AuthPlugin<Player> {
private final CrazyLogin crazyLoginPlugin;
private final PlayerListener playerListener;
protected CrazyLoginHook(FastLoginBukkit plugin) {
public CrazyLoginHook(FastLoginBukkit plugin) {
this.plugin = plugin;
crazyLoginPlugin = CrazyLogin.getPlugin();

View File

@@ -1,6 +1,5 @@
package com.github.games647.fastlogin.bukkit.hook;
package com.github.games647.fastlogin.bukkit.hooks;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import io.github.lucaseasedup.logit.CancelledState;
@@ -23,21 +22,11 @@ import org.bukkit.entity.Player;
*/
public class LogItHook implements AuthPlugin<Player> {
private final FastLoginBukkit plugin;
protected LogItHook(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@Override
public boolean forceLogin(Player player) {
SessionManager sessionManager = LogItCore.getInstance().getSessionManager();
if (sessionManager.isSessionAlive(player)) {
plugin.getLog().warn(ALREADY_AUTHENTICATED, player);
return false;
}
return sessionManager.startSession(player) == CancelledState.NOT_CANCELLED;
return sessionManager.isSessionAlive(player)
|| sessionManager.startSession(player) == CancelledState.NOT_CANCELLED;
}
@Override

View File

@@ -1,4 +1,4 @@
package com.github.games647.fastlogin.bukkit.hook;
package com.github.games647.fastlogin.bukkit.hooks;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
@@ -11,7 +11,7 @@ import com.lenis0012.bukkit.loginsecurity.session.action.RegisterAction;
import org.bukkit.entity.Player;
/**
* GitHub: https://github.com/lenis0012/LoginSecurity-2
* GitHub: https://github.com/lenis0012/LoginSecurity-2
* <p>
* Project page:
* <p>
@@ -23,20 +23,15 @@ public class LoginSecurityHook implements AuthPlugin<Player> {
private final FastLoginBukkit plugin;
protected LoginSecurityHook(FastLoginBukkit plugin) {
public LoginSecurityHook(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@Override
public boolean forceLogin(Player player) {
PlayerSession session = LoginSecurity.getSessionManager().getPlayerSession(player);
if (session.isAuthorized()) {
plugin.getLog().warn(ALREADY_AUTHENTICATED, player);
return true;
}
return session.isAuthorized() || session.performAction(new LoginAction(AuthService.PLUGIN, plugin)).isSuccess();
return session.isAuthorized()
|| session.performAction(new LoginAction(AuthService.PLUGIN, plugin)).isSuccess();
}
@Override

View File

@@ -1,4 +1,4 @@
package com.github.games647.fastlogin.bukkit.hook;
package com.github.games647.fastlogin.bukkit.hooks;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
@@ -25,7 +25,7 @@ public class UltraAuthHook implements AuthPlugin<Player> {
private final Plugin ultraAuthPlugin = Main.main;
private final FastLoginBukkit plugin;
protected UltraAuthHook(FastLoginBukkit plugin) {
public UltraAuthHook(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@@ -34,8 +34,7 @@ public class UltraAuthHook implements AuthPlugin<Player> {
//not thread-safe
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(plugin, () -> {
if (UltraAuthAPI.isAuthenticated(player)) {
plugin.getLog().warn(ALREADY_AUTHENTICATED, player);
return false;
return true;
}
UltraAuthAPI.authenticatedPlayer(player);

View File

@@ -1,4 +1,4 @@
package com.github.games647.fastlogin.bukkit.hook;
package com.github.games647.fastlogin.bukkit.hooks;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
@@ -24,7 +24,7 @@ public class xAuthHook implements AuthPlugin<Player> {
private final xAuth xAuthPlugin = xAuth.getPlugin();
private final FastLoginBukkit plugin;
protected xAuthHook(FastLoginBukkit plugin) {
public xAuthHook(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@@ -35,8 +35,7 @@ public class xAuthHook implements AuthPlugin<Player> {
xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(player);
if (xAuthPlayer != null) {
if (xAuthPlayer.isAuthenticated()) {
plugin.getLog().warn(ALREADY_AUTHENTICATED, player);
return false;
return true;
}
//we checked that the player is premium (paid account)

View File

@@ -0,0 +1,141 @@
package com.github.games647.fastlogin.bukkit.listener;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.tasks.ForceLoginTask;
import com.github.games647.fastlogin.core.PremiumStatus;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import com.github.games647.fastlogin.core.messages.LoginActionMessage;
import com.github.games647.fastlogin.core.messages.LoginActionMessage.Type;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Stream;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener;
import static java.util.stream.Collectors.toSet;
/**
* Responsible for receiving messages from a BungeeCord instance.
*
* 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 {
private static final String FILE_NAME = "proxy-whitelist.txt";
private final FastLoginBukkit plugin;
//null if whitelist is empty so bungeecord support is disabled
private final Set<UUID> proxyIds;
public BungeeListener(FastLoginBukkit plugin) {
this.plugin = plugin;
this.proxyIds = loadBungeeCordIds();
}
@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
if (!channel.equals(plugin.getName())) {
return;
}
ByteArrayDataInput dataInput = ByteStreams.newDataInput(message);
String subChannel = dataInput.readUTF();
if (!"LoginAction".equals(subChannel)) {
plugin.getLog().info("Unknown sub channel {}", subChannel);
return;
}
LoginActionMessage loginMessage = new LoginActionMessage();
loginMessage.readFrom(dataInput);
plugin.getLog().debug("Received plugin message {}", loginMessage);
//check if the player is still online or disconnected
Player checkedPlayer = Bukkit.getPlayerExact(loginMessage.getPlayerName());
if (checkedPlayer == null) {
return;
}
//fail if target player is blacklisted because already authenticated or wrong bungeecord id
if (checkedPlayer.hasMetadata(plugin.getName())) {
//fail if BungeeCord support is disabled (id = null)
UUID sourceId = loginMessage.getProxyId();
if (proxyIds.contains(sourceId)) {
readMessage(checkedPlayer, loginMessage);
} else {
plugin.getLog().warn("Received proxy id: {} that doesn't exist in the proxy whitelist file", sourceId);
}
} else {
plugin.getLog().warn("Received message {} from a blacklisted player {}", loginMessage, checkedPlayer);
}
}
private void readMessage(Player player, LoginActionMessage message) {
String playerName = message.getPlayerName();
Type type = message.getType();
InetSocketAddress address = player.getAddress();
String id = '/' + address.getAddress().getHostAddress() + ':' + address.getPort();
if (type == Type.LOGIN) {
plugin.getPremiumPlayers().put(player.getUniqueId(), PremiumStatus.PREMIUM);
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, true);
playerSession.setVerified(true);
plugin.getLoginSessions().put(id, playerSession);
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, new ForceLoginTask(plugin.getCore(), player), 20L);
} else if (type == Type.REGISTER) {
plugin.getPremiumPlayers().put(player.getUniqueId(), PremiumStatus.PREMIUM);
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> {
AuthPlugin<Player> authPlugin = plugin.getCore().getAuthPluginHook();
try {
//we need to check if the player is registered on Bukkit too
if (authPlugin == null || !authPlugin.isRegistered(playerName)) {
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, false);
playerSession.setVerified(true);
plugin.getLoginSessions().put(id, playerSession);
new ForceLoginTask(plugin.getCore(), player).run();
}
} catch (Exception ex) {
plugin.getLog().error("Failed to query isRegistered for player: {}", player, ex);
}
}, 20L);
} else if (type == Type.CRACKED) {
plugin.getPremiumPlayers().put(player.getUniqueId(), PremiumStatus.CRACKED);
}
}
public Set<UUID> loadBungeeCordIds() {
Path whitelistFile = plugin.getPluginFolder().resolve(FILE_NAME);
try {
if (Files.notExists(whitelistFile)) {
Files.createFile(whitelistFile);
}
try (Stream<String> lines = Files.lines(whitelistFile)) {
return lines.map(String::trim)
.map(UUID::fromString)
.collect(toSet());
}
} catch (IOException ex) {
plugin.getLog().error("Failed to create file for Proxy whitelist", ex);
} catch (Exception ex) {
plugin.getLog().error("Failed to retrieve proxy Id. Disabling BungeeCord support", ex);
}
return Collections.emptySet();
}
}

View File

@@ -1,16 +1,13 @@
package com.github.games647.fastlogin.bukkit.listener;
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.ForceLoginTask;
import com.github.games647.fastlogin.bukkit.tasks.ForceLoginTask;
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;
/**
@@ -27,42 +24,23 @@ public class ConnectionListener implements Listener {
this.plugin = 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, () -> {
// session exists so the player is ready for force login
// cases: Paper (firing proxy message before PlayerJoinEvent) or not running proxy and already
// having the login session from the login process
BukkitLoginSession session = plugin.getSessionManager().getLoginSession(player.getAddress());
if (session != null) {
Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player, session);
Bukkit.getScheduler().runTaskAsynchronously(plugin, forceLoginTask);
}
plugin.getProxyManager().markJoinEventFired(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);
if (!plugin.isBungeeEnabled()) {
//Wait before auth plugin and we received a message from BungeeCord initializes the player
Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player);
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, forceLoginTask, DELAY_LOGIN);
}
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent quitEvent) {
Player player = quitEvent.getPlayer();
player.removeMetadata(plugin.getName(), plugin);
removeBlockedStatus(player);
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
plugin.getPremiumPlayers().remove(player.getUniqueId());
plugin.getProxyManager().cleanup(player);
}
private void removeBlockedStatus(Player player) {
player.removeMetadata(plugin.getName(), plugin);
}
}

View File

@@ -1,42 +0,0 @@
package com.github.games647.fastlogin.bukkit.listener;
import com.destroystokyo.paper.profile.ProfileProperty;
import com.github.games647.craftapi.model.skin.Textures;
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
import org.bukkit.event.player.AsyncPlayerPreLoginEvent.Result;
public class PaperPreLoginListener implements Listener {
private final FastLoginBukkit plugin;
public PaperPreLoginListener(final FastLoginBukkit plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.HIGHEST)
//if paper is used - player skin must be set at pre login, otherwise usercache is used
//using usercache makes premium name change basically impossible
public void onAsyncPlayerPreLogin(AsyncPlayerPreLoginEvent event) {
if (event.getLoginResult() != Result.ALLOWED) {
return;
}
// event gives us only IP, not the port, so we need to loop through all the sessions
for (BukkitLoginSession session : plugin.getSessionManager().getLoginSessions().values()) {
if (!event.getName().equals(session.getUsername())) {
continue;
}
session.getSkin().ifPresent(skin -> event.getPlayerProfile().setProperty(new ProfileProperty(Textures.KEY,
skin.getValue(), skin.getSignature())));
break;
}
}
}

View File

@@ -1,6 +1,6 @@
package com.github.games647.fastlogin.bukkit.auth.protocolsupport;
package com.github.games647.fastlogin.bukkit.listener.protocolsupport;
import com.github.games647.fastlogin.core.auth.LoginSource;
import com.github.games647.fastlogin.core.shared.LoginSource;
import java.net.InetSocketAddress;
@@ -10,7 +10,7 @@ public class ProtocolLoginSource implements LoginSource {
private final PlayerLoginStartEvent loginStartEvent;
protected ProtocolLoginSource(PlayerLoginStartEvent loginStartEvent) {
public ProtocolLoginSource(PlayerLoginStartEvent loginStartEvent) {
this.loginStartEvent = loginStartEvent;
}

View File

@@ -0,0 +1,82 @@
package com.github.games647.fastlogin.bukkit.listener.protocolsupport;
import com.github.games647.craftapi.model.skin.SkinProperty;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.shared.JoinManagement;
import java.net.InetSocketAddress;
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.PlayerPropertiesResolveEvent;
public class ProtocolSupportListener extends JoinManagement<Player, CommandSender, ProtocolLoginSource>
implements Listener {
private final FastLoginBukkit plugin;
public ProtocolSupportListener(FastLoginBukkit plugin) {
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
this.plugin = plugin;
}
@EventHandler
public void onLoginStart(PlayerLoginStartEvent loginStartEvent) {
if (loginStartEvent.isLoginDenied() || plugin.getCore().getAuthPluginHook() == null) {
return;
}
String username = loginStartEvent.getName();
InetSocketAddress address = loginStartEvent.getAddress();
//remove old data every time on a new login in order to keep the session only for one person
plugin.getLoginSessions().remove(address.toString());
super.onLogin(username, new ProtocolLoginSource(loginStartEvent));
}
@EventHandler
public void onConnectionClosed(ConnectionCloseEvent closeEvent) {
InetSocketAddress address = closeEvent.getConnection().getAddress();
plugin.getLoginSessions().remove(address.toString());
}
@EventHandler
public void onPropertiesResolve(PlayerPropertiesResolveEvent propertiesResolveEvent) {
InetSocketAddress address = propertiesResolveEvent.getAddress();
BukkitLoginSession session = plugin.getLoginSessions().get(address.toString());
//skin was resolved -> premium player
if (propertiesResolveEvent.hasProperty(SkinProperty.TEXTURE_KEY) && session != null) {
session.setVerified(true);
}
}
@Override
public void requestPremiumLogin(ProtocolLoginSource source, StoredProfile profile, String username
, boolean registered) {
source.setOnlineMode();
String ip = source.getAddress().getAddress().getHostAddress();
plugin.getCore().getPendingLogin().put(ip + username, new Object());
BukkitLoginSession playerSession = new BukkitLoginSession(username, registered, profile);
plugin.getLoginSessions().put(source.getAddress().toString(), playerSession);
if (plugin.getConfig().getBoolean("premiumUuid")) {
source.getLoginStartEvent().setUseOnlineModeUUID(true);
}
}
@Override
public void startCrackedSession(ProtocolLoginSource source, StoredProfile profile, String username) {
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
plugin.getLoginSessions().put(source.getAddress().toString(), loginSession);
}
}

View File

@@ -1,6 +1,12 @@
package com.github.games647.fastlogin.bukkit.hook;
package com.github.games647.fastlogin.bukkit.tasks;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.hooks.AuthMeHook;
import com.github.games647.fastlogin.bukkit.hooks.CrazyLoginHook;
import com.github.games647.fastlogin.bukkit.hooks.LogItHook;
import com.github.games647.fastlogin.bukkit.hooks.LoginSecurityHook;
import com.github.games647.fastlogin.bukkit.hooks.UltraAuthHook;
import com.github.games647.fastlogin.bukkit.hooks.xAuthHook;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import java.lang.reflect.Constructor;
@@ -22,12 +28,12 @@ public class DelayedAuthHook implements Runnable {
@Override
public void run() {
boolean hookFound = isHookFound();
if (plugin.getProxyManager().isEnabled()) {
plugin.getLog().info("Proxy setting detected. No auth plugin is required");
if (plugin.isBungeeEnabled()) {
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 (or similar proxies) is deactivated. "
+ "and BungeeCord is deactivated. "
+ "Either one or both of the checks have to pass in order to use this plugin");
}
}
@@ -60,8 +66,8 @@ public class DelayedAuthHook implements Runnable {
try {
@SuppressWarnings("unchecked")
List<Class<? extends AuthPlugin<Player>>> hooks = Arrays.asList(AuthMeHook.class,
CrazyLoginHook.class, LogItHook.class, LoginSecurityHook.class,
SodionAuthHook.class, UltraAuthHook.class, xAuthHook.class);
CrazyLoginHook.class, LogItHook.class, LoginSecurityHook.class, UltraAuthHook.class,
xAuthHook.class);
for (Class<? extends AuthPlugin<Player>> clazz : hooks) {
String pluginName = clazz.getSimpleName().replace("Hook", "");

View File

@@ -0,0 +1,65 @@
package com.github.games647.fastlogin.bukkit.tasks;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.messages.SuccessMessage;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.shared.ForceLoginManagement;
import com.github.games647.fastlogin.core.shared.LoginSession;
import java.util.concurrent.ExecutionException;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.metadata.FixedMetadataValue;
public class ForceLoginTask extends ForceLoginManagement<Player, CommandSender, BukkitLoginSession, FastLoginBukkit> {
public ForceLoginTask(FastLoginCore<Player, CommandSender, FastLoginBukkit> core, Player player) {
super(core, player, getSession(core.getPlugin(), player));
}
private static BukkitLoginSession getSession(FastLoginBukkit plugin, Player player) {
//remove the bungeecord identifier if there is ones
String id = '/' + player.getAddress().getAddress().getHostAddress() + ':' + player.getAddress().getPort();
return plugin.getLoginSessions().remove(id);
}
@Override
public void run() {
//blacklist this target player for BungeeCord Id brute force attacks
FastLoginBukkit plugin = core.getPlugin();
player.setMetadata(core.getPlugin().getName(), new FixedMetadataValue(plugin, true));
super.run();
}
@Override
public void onForceActionSuccess(LoginSession session) {
if (core.getPlugin().isBungeeEnabled()) {
core.getPlugin().sendPluginMessage(player, new SuccessMessage());
}
}
@Override
public String getName(Player player) {
return player.getName();
}
@Override
public boolean isOnline(Player player) {
try {
//the player-list isn't thread-safe
return Bukkit.getScheduler().callSyncMethod(core.getPlugin(), player::isOnline).get();
} catch (InterruptedException | ExecutionException ex) {
core.getPlugin().getLog().error("Failed to perform thread-safe online check for {}", player, ex);
return false;
}
}
@Override
public boolean isOnlineMode() {
return session.isVerified() && player.getName().equals(session.getUsername());
}
}

View File

@@ -11,22 +11,14 @@ description: |
website: ${project.url}
dev-url: ${project.url}
# This plugin don't have to be transformed for compatibility with Minecraft >= 1.13
api-version: '1.13'
# Load the plugin as early as possible to inject it for all players
load: STARTUP
softdepend:
# We depend either ProtocolLib or ProtocolSupport
- ProtocolSupport
- ProtocolLib
# Premium variable
- PlaceholderAPI
# Auth plugins
- AuthMe
- LoginSecurity
- xAuth
- LogIt
- UltraAuth
- CrazyLogin
commands:
${project.parent.name}:
@@ -36,7 +28,7 @@ commands:
permission: ${project.artifactId}.command.premium
cracked:
description: 'Label the invoker or the player specified as cracked if marked premium before'
description: 'Label the invoker or the player specified as cracked if he was marked premium before'
aliases: [unpremium]
usage: /<command> [player]
permission: ${project.artifactId}.command.cracked

View File

@@ -1,43 +0,0 @@
package com.github.games647.fastlogin.bukkit.listener.protocollib;
import com.github.games647.fastlogin.bukkit.auth.protocollib.EncryptionUtil;
import java.security.SecureRandom;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
public class EncryptionUtilTest {
@Test
public void testVerifyToken() throws Exception {
SecureRandom random = new SecureRandom();
byte[] token = EncryptionUtil.generateVerifyToken(random);
assertThat(token, notNullValue());
assertThat(token.length, is(4));
}
// @Test
// public void testDecryptSharedSecret() throws Exception {
//
// }
//
// @Test
// public void testDecryptData() throws Exception {
//
// }
// private static SecretKey createNewSharedKey() {
// try {
// KeyGenerator keygenerator = KeyGenerator.getInstance("AES");
// keygenerator.init(128);
// return keygenerator.generateKey();
// } catch (NoSuchAlgorithmException nosuchalgorithmexception) {
// throw new Error(nosuchalgorithmexception);
// }
// }
}

Binary file not shown.

View File

@@ -1,11 +1,11 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.games647</groupId>
<artifactId>fastlogin</artifactId>
<version>1.11-SNAPSHOT</version>
<version>1.11</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -21,7 +21,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<version>3.1.0</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<shadedArtifactAttached>false</shadedArtifactAttached>
@@ -56,24 +56,18 @@
</build>
<repositories>
<repository>
<id>vik1395-repo</id>
<url>https://vik1395.github.io/repo.vik1395.me/repositories</url>
</repository>
<repository>
<id>codemc-repo</id>
<url>https://repo.codemc.io/repository/maven-public/</url>
</repository>
<repository>
<id>nukkitx-repo</id>
<url>https://repo.nukkitx.com/maven-snapshots/</url>
</repository>
<repository>
<id>spigotplugins-repo</id>
<url>https://maven.gamestrike.de/mvn/</url>
<url>https://repo.codemc.org/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<!--Common plugin component-->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>fastlogin.core</artifactId>
@@ -84,32 +78,21 @@
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-proxy</artifactId>
<version>1.16-R0.5-SNAPSHOT</version>
<version>1.12-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- Bedrock player bridge -->
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>floodgate-bungee</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!--Login plugin-->
<dependency>
<groupId>me.vik1395</groupId>
<artifactId>BungeeAuth</artifactId>
<version>1.4</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/BungeeAuth-1.4.jar</systemPath>
</dependency>
<dependency>
<groupId>de.xxschrandxx.bca</groupId>
<artifactId>BungeeCordAuthenticator</artifactId>
<version>0.0.2-SNAPSHOT</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View File

@@ -1,7 +1,7 @@
package com.github.games647.fastlogin.bungee;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.LoginSession;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.shared.LoginSession;
public class BungeeLoginSession extends LoginSession {
@@ -12,28 +12,28 @@ public class BungeeLoginSession extends LoginSession {
super(username, registered, profile);
}
public synchronized void setRegistered(boolean registered) {
public void setRegistered(boolean registered) {
this.registered = registered;
}
public synchronized boolean isAlreadySaved() {
public boolean isAlreadySaved() {
return alreadySaved;
}
public synchronized void setAlreadySaved(boolean alreadySaved) {
public void setAlreadySaved(boolean alreadySaved) {
this.alreadySaved = alreadySaved;
}
public synchronized boolean isAlreadyLogged() {
public boolean isAlreadyLogged() {
return alreadyLogged;
}
public synchronized void setAlreadyLogged(boolean alreadyLogged) {
public void setAlreadyLogged(boolean alreadyLogged) {
this.alreadyLogged = alreadyLogged;
}
@Override
public synchronized String toString() {
public String toString() {
return this.getClass().getSimpleName() + '{' +
"alreadySaved=" + alreadySaved +
", alreadyLogged=" + alreadyLogged +

View File

@@ -1,23 +1,18 @@
package com.github.games647.fastlogin.bungee;
import com.github.games647.fastlogin.core.auth.LoginSource;
import com.github.games647.fastlogin.core.shared.LoginSource;
import java.net.InetSocketAddress;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.event.PreLoginEvent;
public class BungeeLoginSource implements LoginSource {
private final PendingConnection connection;
private final PreLoginEvent preLoginEvent;
public BungeeLoginSource(PendingConnection connection, PreLoginEvent preLoginEvent) {
public BungeeLoginSource(PendingConnection connection) {
this.connection = connection;
this.preLoginEvent = preLoginEvent;
}
@Override
@@ -27,13 +22,7 @@ public class BungeeLoginSource implements LoginSource {
@Override
public void kick(String message) {
preLoginEvent.setCancelled(true);
if (message == null) {
preLoginEvent.setCancelReason(new ComponentBuilder("Kicked").color(ChatColor.WHITE).create());
} else {
preLoginEvent.setCancelReason(TextComponent.fromLegacyText(message));
}
connection.disconnect(TextComponent.fromLegacyText(message));
}
@Override

View File

@@ -1,23 +0,0 @@
package com.github.games647.fastlogin.bungee;
import com.github.games647.fastlogin.core.SessionManager;
import java.util.UUID;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
public class BungeeSessionManager extends SessionManager<PlayerDisconnectEvent, PendingConnection, BungeeLoginSession>
implements Listener {
//todo: memory leak on cancelled login event
@EventHandler
public void onPlayQuit(PlayerDisconnectEvent disconnectEvent) {
ProxiedPlayer player = disconnectEvent.getPlayer();
UUID playerId = player.getUniqueId();
endPlaySession(playerId);
}
}

View File

@@ -1,30 +1,27 @@
package com.github.games647.fastlogin.bungee;
import com.github.games647.fastlogin.bungee.hook.BungeeAuthHook;
import com.github.games647.fastlogin.bungee.hook.BungeeCordAuthenticatorHook;
import com.github.games647.fastlogin.bungee.hooks.BungeeAuthHook;
import com.github.games647.fastlogin.bungee.listener.ConnectListener;
import com.github.games647.fastlogin.bungee.listener.PluginMessageListener;
import com.github.games647.fastlogin.core.AsyncScheduler;
import com.github.games647.fastlogin.bungee.listener.MessageListener;
import com.github.games647.fastlogin.core.CommonUtil;
import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
import com.github.games647.fastlogin.core.message.ChannelMessage;
import com.github.games647.fastlogin.core.message.NamespaceKey;
import com.github.games647.fastlogin.core.message.SuccessMessage;
import com.github.games647.fastlogin.core.messages.ChannelMessage;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.shared.PlatformPlugin;
import com.google.common.collect.MapMaker;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.nio.file.Path;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ThreadFactory;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginManager;
import net.md_5.bungee.api.scheduler.GroupedThreadFactory;
import org.slf4j.Logger;
@@ -34,16 +31,14 @@ import org.slf4j.Logger;
*/
public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSender> {
private final BungeeSessionManager sessionManager = new BungeeSessionManager();
private final ConcurrentMap<PendingConnection, BungeeLoginSession> session = new MapMaker().weakKeys().makeMap();
private FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core;
private AsyncScheduler scheduler;
private Logger logger;
@Override
public void onEnable() {
logger = CommonUtil.createLoggerFromJDK(getLogger());
scheduler = new AsyncScheduler(logger, getThreadFactory());
core = new FastLoginCore<>(this);
core.load();
@@ -52,16 +47,11 @@ public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSen
}
//events
PluginManager pluginManager = getProxy().getPluginManager();
boolean floodgateAvail = pluginManager.getPlugin("floodgate") != null;
ConnectListener connectListener = new ConnectListener(this, core.getRateLimiter(), floodgateAvail);
getProxy().getPluginManager().registerListener(this, new ConnectListener(this));
getProxy().getPluginManager().registerListener(this, new MessageListener(this));
pluginManager.registerListener(this, connectListener);
pluginManager.registerListener(this, new PluginMessageListener(this));
//this is required to listen to incoming messages from the server
getProxy().registerChannel(NamespaceKey.getCombined(getName(), ChangePremiumMessage.CHANGE_CHANNEL));
getProxy().registerChannel(NamespaceKey.getCombined(getName(), SuccessMessage.SUCCESS_CHANNEL));
//this is required to listen to messages from the server
getProxy().registerChannel(getName());
registerHook();
}
@@ -77,27 +67,25 @@ public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSen
return core;
}
public ConcurrentMap<PendingConnection, BungeeLoginSession> getSession() {
return session;
}
private void registerHook() {
Plugin BungeeAuth = getProxy().getPluginManager().getPlugin("BungeeAuth");
if (BungeeAuth != null) {
Plugin plugin = getProxy().getPluginManager().getPlugin("BungeeAuth");
if (plugin != null) {
core.setAuthPluginHook(new BungeeAuthHook());
logger.info("Hooked into BungeeAuth");
}
Plugin BungeeCordAuthenticatorBungee = getProxy().getPluginManager().getPlugin("BungeeCordAuthenticatorBungee");
if (BungeeCordAuthenticatorBungee != null) {
logger.info("Try to hook into BungeeCordAuthenticatorBungee...");
BungeeCordAuthenticatorHook hook = new BungeeCordAuthenticatorHook(BungeeCordAuthenticatorBungee, logger);
core.setAuthPluginHook(hook);
}
}
public void sendPluginMessage(Server server, ChannelMessage message) {
if (server != null) {
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
message.writeTo(dataOutput);
dataOutput.writeUTF(message.getChannelName());
NamespaceKey channel = new NamespaceKey(getName(), message.getChannelName());
server.sendData(channel.getCombinedName(), dataOutput.toByteArray());
message.writeTo(dataOutput);
server.sendData(core.getPlugin().getName(), dataOutput.toByteArray());
}
}
@@ -125,15 +113,10 @@ public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSen
@SuppressWarnings("deprecation")
public ThreadFactory getThreadFactory() {
return new ThreadFactoryBuilder()
.setNameFormat(getName() + " Pool Thread #%1$d")
.setNameFormat(core.getPlugin().getName() + " Database Pool Thread #%1$d")
//Hikari create daemons by default
.setDaemon(true)
.setThreadFactory(new GroupedThreadFactory(this, getName()))
.build();
}
@Override
public AsyncScheduler getScheduler() {
return scheduler;
}
}

View File

@@ -1,39 +0,0 @@
package com.github.games647.fastlogin.bungee.event;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.LoginSession;
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
import net.md_5.bungee.api.plugin.Cancellable;
import net.md_5.bungee.api.plugin.Event;
public class BungeeFastLoginAutoLoginEvent extends Event implements FastLoginAutoLoginEvent, Cancellable {
private final LoginSession session;
private final StoredProfile profile;
private boolean cancelled;
public BungeeFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile) {
this.session = session;
this.profile = profile;
}
@Override
public LoginSession getSession() {
return session;
}
@Override
public StoredProfile getProfile() {
return profile;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
}

View File

@@ -1,34 +0,0 @@
package com.github.games647.fastlogin.bungee.event;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.LoginSource;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import net.md_5.bungee.api.plugin.Event;
public class BungeeFastLoginPreLoginEvent extends Event implements FastLoginPreLoginEvent {
private final String username;
private final LoginSource source;
private final StoredProfile profile;
public BungeeFastLoginPreLoginEvent(String username, LoginSource source, StoredProfile profile) {
this.username = username;
this.source = source;
this.profile = profile;
}
@Override
public String getUsername() {
return username;
}
@Override
public LoginSource getSource() {
return source;
}
@Override
public StoredProfile getProfile() {
return profile;
}
}

View File

@@ -1,26 +0,0 @@
package com.github.games647.fastlogin.bungee.event;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent;
import net.md_5.bungee.api.plugin.Event;
public class BungeeFastLoginPremiumToggleEvent extends Event implements FastLoginPremiumToggleEvent {
private final StoredProfile profile;
private final PremiumToggleReason reason;
public BungeeFastLoginPremiumToggleEvent(StoredProfile profile, PremiumToggleReason reason) {
this.profile = profile;
this.reason = reason;
}
@Override
public StoredProfile getProfile() {
return profile;
}
@Override
public FastLoginPremiumToggleEvent.PremiumToggleReason getReason() {
return reason;
}
}

View File

@@ -1,70 +0,0 @@
package com.github.games647.fastlogin.bungee.hook;
import java.sql.SQLException;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import org.slf4j.Logger;
import de.xxschrandxx.bca.bungee.BungeeCordAuthenticatorBungee;
import de.xxschrandxx.bca.bungee.api.BungeeCordAuthenticatorBungeeAPI;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin;
/**
* GitHub:
* https://github.com/xXSchrandXx/SpigotPlugins/tree/master/BungeeCordAuthenticator
*
* Project page:
*
* Spigot: https://www.spigotmc.org/resources/bungeecordauthenticator.87669/
*/
public class BungeeCordAuthenticatorHook implements AuthPlugin<ProxiedPlayer> {
public final BungeeCordAuthenticatorBungeeAPI api;
public BungeeCordAuthenticatorHook(Plugin plugin, Logger logger) {
BungeeCordAuthenticatorBungee bcab = (BungeeCordAuthenticatorBungee) plugin;
api = bcab.getAPI();
logger.info("BungeeCordAuthenticatorHook | Hooked successful!");
}
@Override
public boolean forceLogin(ProxiedPlayer player) {
if (api.isAuthenticated(player)) {
return true;
} else {
try {
api.setAuthenticated(player);
}
catch (SQLException e) {
e.printStackTrace();
return false;
}
return true;
}
}
@Override
public boolean isRegistered(String playerName) {
try {
return api.getSQL().checkPlayerEntry(playerName);
}
catch (SQLException e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean forceRegister(ProxiedPlayer player, String password) {
try {
return api.createPlayerEntry(player, password);
}
catch (SQLException e) {
e.printStackTrace();
return false;
}
}
}

View File

@@ -1,4 +1,4 @@
package com.github.games647.fastlogin.bungee.hook;
package com.github.games647.fastlogin.bungee.hooks;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;

View File

@@ -1,21 +1,16 @@
package com.github.games647.fastlogin.bungee.listener;
import com.github.games647.craftapi.UUIDAdapter;
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.bungee.task.AsyncPremiumCheck;
import com.github.games647.fastlogin.bungee.task.ForceLoginTask;
import com.github.games647.fastlogin.core.auth.RateLimiter;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.LoginSession;
import com.google.common.base.Throwables;
import com.github.games647.fastlogin.bungee.tasks.AsyncPremiumCheck;
import com.github.games647.fastlogin.bungee.tasks.ForceLoginTask;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.shared.LoginSession;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Field;
import java.util.UUID;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
@@ -30,75 +25,30 @@ import net.md_5.bungee.connection.LoginResult.Property;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import org.geysermc.floodgate.FloodgateAPI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Enables online mode logins for specified users and sends plugin message to the Bukkit version of this plugin in
* order to clear that the connection is online mode.
*/
public class ConnectListener implements Listener {
private static final String UUID_FIELD_NAME = "uniqueId";
private static final boolean initialHandlerClazzFound;
private static final MethodHandle uniqueIdSetter;
static {
MethodHandle setHandle = null;
boolean handlerFound = false;
try {
Lookup lookup = MethodHandles.lookup();
Class.forName("net.md_5.bungee.connection.InitialHandler");
handlerFound = true;
Field uuidField = InitialHandler.class.getDeclaredField(UUID_FIELD_NAME);
uuidField.setAccessible(true);
setHandle = lookup.unreflectSetter(uuidField);
} catch (ClassNotFoundException classNotFoundException) {
Logger logger = LoggerFactory.getLogger(ConnectListener.class);
logger.error(
"Cannot find Bungee initial handler; Disabling premium UUID and skin won't work.",
classNotFoundException
);
} catch (ReflectiveOperationException reflectiveOperationException) {
reflectiveOperationException.printStackTrace();
}
initialHandlerClazzFound = handlerFound;
uniqueIdSetter = setHandle;
}
private final FastLoginBungee plugin;
private final RateLimiter rateLimiter;
private final Property[] emptyProperties = {};
private final boolean floodGateAvailable;
public ConnectListener(FastLoginBungee plugin, RateLimiter rateLimiter, boolean floodgateAvailable) {
public ConnectListener(FastLoginBungee plugin) {
this.plugin = plugin;
this.rateLimiter = rateLimiter;
this.floodGateAvailable = floodgateAvailable;
}
@EventHandler
public void onPreLogin(PreLoginEvent preLoginEvent) {
PendingConnection connection = preLoginEvent.getConnection();
if (preLoginEvent.isCancelled() || isBedrockPlayer(connection.getUniqueId())) {
if (preLoginEvent.isCancelled()) {
return;
}
if (!rateLimiter.tryAcquire()) {
plugin.getLog().warn("Join limit hit - Ignoring player {}", connection);
return;
}
String username = connection.getName();
plugin.getLog().info("Incoming login request for {} from {}", username, connection.getSocketAddress());
preLoginEvent.registerIntent(plugin);
Runnable asyncPremiumCheck = new AsyncPremiumCheck(plugin, preLoginEvent, connection, username);
plugin.getScheduler().runAsync(asyncPremiumCheck);
PendingConnection connection = preLoginEvent.getConnection();
Runnable asyncPremiumCheck = new AsyncPremiumCheck(plugin, preLoginEvent, connection);
ProxyServer.getInstance().getScheduler().runAsync(plugin, asyncPremiumCheck);
}
@EventHandler(priority = EventPriority.LOWEST)
@@ -110,69 +60,48 @@ public class ConnectListener implements Listener {
//use the login event instead of the post login event in order to send the login success packet to the client
//with the offline uuid this makes it possible to set the skin then
PendingConnection connection = loginEvent.getConnection();
InitialHandler initialHandler = (InitialHandler) connection;
String username = initialHandler.getLoginRequest().getData();
if (connection.isOnlineMode()) {
LoginSession session = plugin.getSession().get(connection);
UUID verifiedUUID = connection.getUniqueId();
String verifiedUsername = connection.getName();
session.setUuid(verifiedUUID);
session.setVerifiedUsername(verifiedUsername);
session.setUuid(connection.getUniqueId());
StoredProfile playerProfile = session.getProfile();
playerProfile.setId(verifiedUUID);
playerProfile.setId(connection.getUniqueId());
// bungeecord will do this automatically so override it on disabled option
if (uniqueIdSetter != null) {
InitialHandler initialHandler = (InitialHandler) connection;
//bungeecord will do this automatically so override it on disabled option
if (!plugin.getCore().getConfig().get("premiumUuid", true)) {
try {
UUID offlineUUID = UUIDAdapter.generateOfflineId(username);
if (!plugin.getCore().getConfig().get("premiumUuid", true)) {
setOfflineId(initialHandler, verifiedUsername);
//bungeecord doesn't support overriding the premium uuid
//so we have to do it with reflection
Field idField = InitialHandler.class.getDeclaredField("uniqueId");
idField.setAccessible(true);
idField.set(connection, offlineUUID);
} catch (NoSuchFieldException | IllegalAccessException ex) {
plugin.getLog().error("Failed to set offline uuid of {}", username, ex);
}
}
if (!plugin.getCore().getConfig().get("forwardSkin", true)) {
// this is null on offline mode
LoginResult loginProfile = initialHandler.getLoginProfile();
if (!plugin.getCore().getConfig().get("forwardSkin", true)) {
//this is null on offline mode
LoginResult loginProfile = initialHandler.getLoginProfile();
if (loginProfile != null) {
loginProfile.setProperties(emptyProperties);
}
}
}
}
private void setOfflineId(InitialHandler connection, String username) {
try {
final UUID oldPremiumId = connection.getUniqueId();
final UUID offlineUUID = UUIDAdapter.generateOfflineId(username);
// BungeeCord only allows setting the UUID in PreLogin events and before requesting online mode
// However if online mode is requested, it will override previous values
// So we have to do it with reflection
uniqueIdSetter.invokeExact(connection, offlineUUID);
String format = "Overridden UUID from {} to {} (based of {}) on {}";
plugin.getLog().info(format, oldPremiumId, offlineUUID, username, connection);
} catch (Exception ex) {
plugin.getLog().error("Failed to set offline uuid of {}", username, ex);
} catch (Throwable throwable) {
// throw remaining exceptions like outofmemory that we shouldn't handle ourself
Throwables.throwIfUnchecked(throwable);
}
}
@EventHandler
public void onServerConnected(ServerConnectedEvent serverConnectedEvent) {
ProxiedPlayer player = serverConnectedEvent.getPlayer();
Server server = serverConnectedEvent.getServer();
BungeeLoginSession session = plugin.getSession().get(player.getPendingConnection());
if (session == null) {
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);
Runnable loginTask = new ForceLoginTask(plugin.getCore(), player, server);
ProxyServer.getInstance().getScheduler().runAsync(plugin, loginTask);
}
@EventHandler
@@ -181,11 +110,4 @@ public class ConnectListener implements Listener {
plugin.getSession().remove(player.getPendingConnection());
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
}
private boolean isBedrockPlayer(UUID correctedUUID) {
// Floodgate will set a correct UUID at the beginning of the PreLoginEvent
// and will cancel the online mode login for those players
// Therefore we just ignore those
return floodGateAvailable && FloodgateAPI.isBedrockPlayer(correctedUUID);
}
}

View File

@@ -2,11 +2,9 @@ package com.github.games647.fastlogin.bungee.listener;
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.bungee.task.AsyncToggleMessage;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
import com.github.games647.fastlogin.core.message.NamespaceKey;
import com.github.games647.fastlogin.core.message.SuccessMessage;
import com.github.games647.fastlogin.bungee.tasks.AsyncToggleMessage;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.messages.ChangePremiumMessage;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
@@ -14,6 +12,7 @@ import com.google.common.io.ByteStreams;
import java.util.Arrays;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
@@ -21,24 +20,18 @@ import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
public class PluginMessageListener implements Listener {
public class MessageListener implements Listener {
private final FastLoginBungee plugin;
private final String successChannel;
private final String changeChannel;
public PluginMessageListener(FastLoginBungee plugin) {
public MessageListener(FastLoginBungee plugin) {
this.plugin = plugin;
this.successChannel = new NamespaceKey(plugin.getName(), SuccessMessage.SUCCESS_CHANNEL).getCombinedName();
this.changeChannel = new NamespaceKey(plugin.getName(), ChangePremiumMessage.CHANGE_CHANNEL).getCombinedName();
}
@EventHandler
public void onPluginMessage(PluginMessageEvent pluginMessageEvent) {
String channel = pluginMessageEvent.getTag();
if (pluginMessageEvent.isCancelled() || !channel.startsWith(plugin.getName().toLowerCase())) {
if (pluginMessageEvent.isCancelled() || !plugin.getName().equals(channel)) {
return;
}
@@ -55,16 +48,17 @@ public class PluginMessageListener implements Listener {
byte[] data = Arrays.copyOf(pluginMessageEvent.getData(), pluginMessageEvent.getData().length);
ProxiedPlayer forPlayer = (ProxiedPlayer) pluginMessageEvent.getReceiver();
plugin.getScheduler().runAsync(() -> readMessage(forPlayer, channel, data));
ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> readMessage(forPlayer, data));
}
private void readMessage(ProxiedPlayer forPlayer, String channel, byte[] data) {
private void readMessage(ProxiedPlayer forPlayer, byte[] data) {
FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core = plugin.getCore();
ByteArrayDataInput dataInput = ByteStreams.newDataInput(data);
if (successChannel.equals(channel)) {
String subChannel = dataInput.readUTF();
if ("Success".equals(subChannel)) {
onSuccessMessage(forPlayer);
} else if (changeChannel.equals(channel)) {
} else if ("ChangeStatus".equals(subChannel)) {
ChangePremiumMessage changeMessage = new ChangePremiumMessage();
changeMessage.readFrom(dataInput);
@@ -81,10 +75,10 @@ public class PluginMessageListener implements Listener {
core.getPendingConfirms().remove(forPlayer.getUniqueId());
Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, true, isSourceInvoker);
plugin.getScheduler().runAsync(task);
ProxyServer.getInstance().getScheduler().runAsync(plugin, task);
} else {
Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, false, isSourceInvoker);
plugin.getScheduler().runAsync(task);
ProxyServer.getInstance().getScheduler().runAsync(plugin, task);
}
}
}

View File

@@ -1,55 +1,46 @@
package com.github.games647.fastlogin.bungee.task;
package com.github.games647.fastlogin.bungee.tasks;
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
import com.github.games647.fastlogin.bungee.BungeeLoginSource;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.bungee.event.BungeeFastLoginPreLoginEvent;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.JoinManagement;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.shared.JoinManagement;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PreLoginEvent;
import net.md_5.bungee.api.event.AsyncEvent;
import net.md_5.bungee.connection.InitialHandler;
public class AsyncPremiumCheck extends JoinManagement<ProxiedPlayer, CommandSender, BungeeLoginSource>
implements Runnable {
private final FastLoginBungee plugin;
private final PreLoginEvent preLoginEvent;
private final AsyncEvent<?> preLoginEvent;
private final String username;
private final PendingConnection connection;
public AsyncPremiumCheck(FastLoginBungee plugin, PreLoginEvent preLoginEvent, PendingConnection connection,
String username) {
public AsyncPremiumCheck(FastLoginBungee plugin, AsyncEvent<?> preLoginEvent, PendingConnection connection) {
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
this.plugin = plugin;
this.preLoginEvent = preLoginEvent;
this.connection = connection;
this.username = username;
}
@Override
public void run() {
plugin.getSession().remove(connection);
InitialHandler initialHandler = (InitialHandler) connection;
String username = initialHandler.getLoginRequest().getData();
try {
super.onLogin(username, new BungeeLoginSource(connection, preLoginEvent));
super.onLogin(username, new BungeeLoginSource(connection));
} finally {
preLoginEvent.completeIntent(plugin);
}
}
@Override
public FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, BungeeLoginSource source,
StoredProfile profile) {
return plugin.getProxy().getPluginManager()
.callEvent(new BungeeFastLoginPreLoginEvent(username, source, profile));
}
@Override
public void requestPremiumLogin(BungeeLoginSource source, StoredProfile profile,
String username, boolean registered) {

View File

@@ -1,11 +1,9 @@
package com.github.games647.fastlogin.bungee.task;
package com.github.games647.fastlogin.bungee.tasks;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.bungee.event.BungeeFastLoginPremiumToggleEvent;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent.PremiumToggleReason;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.TextComponent;
@@ -48,10 +46,6 @@ public class AsyncToggleMessage implements Runnable {
playerProfile.setPremium(false);
playerProfile.setId(null);
core.getStorage().save(playerProfile);
PremiumToggleReason reason = (!isPlayerSender || !sender.getName().equalsIgnoreCase(playerProfile.getName())) ?
PremiumToggleReason.COMMAND_OTHER : PremiumToggleReason.COMMAND_SELF;
core.getPlugin().getProxy().getPluginManager().callEvent(
new BungeeFastLoginPremiumToggleEvent(playerProfile, reason));
sendMessage("remove-premium");
}
@@ -64,10 +58,6 @@ public class AsyncToggleMessage implements Runnable {
playerProfile.setPremium(true);
core.getStorage().save(playerProfile);
PremiumToggleReason reason = (!isPlayerSender || !sender.getName().equalsIgnoreCase(playerProfile.getName())) ?
PremiumToggleReason.COMMAND_OTHER : PremiumToggleReason.COMMAND_SELF;
core.getPlugin().getProxy().getPluginManager().callEvent(
new BungeeFastLoginPremiumToggleEvent(playerProfile, reason));
sendMessage("add-premium");
}

View File

@@ -1,16 +1,13 @@
package com.github.games647.fastlogin.bungee.task;
package com.github.games647.fastlogin.bungee.tasks;
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.bungee.event.BungeeFastLoginAutoLoginEvent;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.message.ChannelMessage;
import com.github.games647.fastlogin.core.message.LoginActionMessage;
import com.github.games647.fastlogin.core.message.LoginActionMessage.Type;
import com.github.games647.fastlogin.core.messages.ChannelMessage;
import com.github.games647.fastlogin.core.messages.LoginActionMessage;
import com.github.games647.fastlogin.core.messages.LoginActionMessage.Type;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.auth.ForceLoginManagement;
import com.github.games647.fastlogin.core.auth.LoginSession;
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
import com.github.games647.fastlogin.core.shared.ForceLoginManagement;
import com.github.games647.fastlogin.core.shared.LoginSession;
import java.util.UUID;
@@ -25,8 +22,8 @@ public class ForceLoginTask
private final Server server;
public ForceLoginTask(FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core,
ProxiedPlayer player, Server server, BungeeLoginSession session) {
super(core, player, session);
ProxiedPlayer player, Server server) {
super(core, player, core.getPlugin().getSession().get(player.getPendingConnection()));
this.server = server;
}
@@ -54,15 +51,10 @@ public class ForceLoginTask
return super.forceLogin(player);
}
@Override
public FastLoginAutoLoginEvent callFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile) {
return core.getPlugin().getProxy().getPluginManager()
.callEvent(new BungeeFastLoginAutoLoginEvent(session, profile));
}
@Override
public boolean forceRegister(ProxiedPlayer player) {
return session.isAlreadyLogged() || super.forceRegister(player);
}
@Override
@@ -70,7 +62,7 @@ public class ForceLoginTask
//sub channel name
Type type = Type.LOGIN;
if (session.needsRegistration()) {
type = Type.REGISTER;
type = Type.LOGIN;
}
UUID proxyId = UUID.fromString(ProxyServer.getInstance().getConfig().getUuid());

View File

@@ -11,7 +11,6 @@ author: games647, https://github.com/games647/FastLogin/graphs/contributors
softDepends:
# BungeeCord auth plugins
- BungeeAuth
- BungeeCordAuthenticatorBungee
description: |
${project.description}

View File

@@ -1,11 +1,11 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.games647</groupId>
<artifactId>fastlogin</artifactId>
<version>1.11-SNAPSHOT</version>
<version>1.11</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -18,13 +18,11 @@
<repository>
<id>luck-repo</id>
<url>https://ci.lucko.me/plugin/repository/everything</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>codemc-repo</id>
<url>https://repo.codemc.io/repository/maven-public/</url>
<url>https://repo.codemc.org/repository/maven-public/</url>
</repository>
</repositories>
@@ -35,14 +33,21 @@
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.1</version>
<version>2.7.8</version>
</dependency>
<!--Logging framework implements slf4j which is required by hikari-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.30</version>
<version>1.7.25</version>
</dependency>
<!--GSON is not at the right position for Minecraft 1.7-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.2.4</version>
</dependency>
<!-- snakeyaml is present in Bungee, Spigot, Cauldron and so we could use this independent implementation -->
@@ -58,25 +63,20 @@
</exclusions>
</dependency>
<!--Common component for contacting the Mojang API-->
<dependency>
<groupId>com.github.games647</groupId>
<artifactId>craftapi</artifactId>
<version>0.4</version>
<version>0.1.3-SNAPSHOT</version>
</dependency>
<!-- APIs we can use because they are available in all platforms (Spigot, Bungee) -->
<!-- APIs we can use because they are available in all platforms (Spigot, Bungee, Cauldron) -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>17.0</version>
<!-- The Uranium project (fork of Cauldron) uses 17.0 like Spigot 1.8 as experimental feature -->
<!-- Project url: https://github.com/UraniumMC/Uranium -->
<version>10.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.2.4</version>
</dependency>
</dependencies>
</project>

View File

@@ -1,69 +0,0 @@
package com.github.games647.fastlogin.core;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
/**
* This limits the number of threads that are used at maximum. Thread creation can be very heavy for the CPU and
* context switching between threads too. However we need many threads for blocking HTTP and database calls.
* Nevertheless this number can be further limited, because the number of actually working database threads
* is limited by the size of our database pool. The goal is to separate concerns into processing and blocking only
* threads.
*/
public class AsyncScheduler {
private static final int MAX_CAPACITY = 1024;
//todo: single thread for delaying and scheduling tasks
private final Logger logger;
// 30 threads are still too many - the optimal solution is to separate into processing and blocking threads
// where processing threads could only be max number of cores while blocking threads could be minimized using
// non-blocking I/O and a single event executor
private final ExecutorService processingPool;
/*
private final ExecutorService databaseExecutor = new ThreadPoolExecutor(1, 10,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(MAX_CAPACITY));
*/
public AsyncScheduler(Logger logger, ThreadFactory threadFactory) {
this.logger = logger;
processingPool = new ThreadPoolExecutor(6, 32,
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(MAX_CAPACITY), threadFactory);
}
/*
public <R> CompletableFuture<R> runDatabaseTask(Supplier<R> databaseTask) {
return CompletableFuture.supplyAsync(databaseTask, databaseExecutor)
.exceptionally(error -> {
logger.warn("Error occurred on thread pool", error);
return null;
})
// change context to the processing pool
.thenApplyAsync(r -> r, processingPool);
}
*/
public CompletableFuture<Void> runAsync(Runnable task) {
return CompletableFuture.runAsync(task, processingPool).exceptionally(error -> {
logger.warn("Error occurred on thread pool", error);
return null;
});
}
public void shutdown() {
MoreExecutors.shutdownAndAwaitTermination(processingPool, 1, TimeUnit.MINUTES);
//MoreExecutors.shutdownAndAwaitTermination(databaseExecutor, 1, TimeUnit.MINUTES);
}
}

View File

@@ -0,0 +1,180 @@
package com.github.games647.fastlogin.core;
import com.github.games647.craftapi.UUIDAdapter;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.Instant;
import java.util.Optional;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.ThreadFactory;
import static java.sql.Statement.RETURN_GENERATED_KEYS;
public class AuthStorage {
private static final String PREMIUM_TABLE = "premium";
private static final String LOAD_BY_NAME = "SELECT * FROM " + PREMIUM_TABLE + " WHERE Name=? LIMIT 1";
private static final String LOAD_BY_UUID = "SELECT * FROM " + PREMIUM_TABLE + " WHERE UUID=? LIMIT 1";
private static final String INSERT_PROFILE = "INSERT INTO " + PREMIUM_TABLE + " (UUID, Name, Premium, LastIp) "
+ "VALUES (?, ?, ?, ?) ";
private static final String UPDATE_PROFILE = "UPDATE " + PREMIUM_TABLE
+ " SET UUID=?, Name=?, Premium=?, LastIp=?, LastLogin=CURRENT_TIMESTAMP WHERE UserID=?";
private final FastLoginCore<?, ?, ?> core;
private final HikariDataSource dataSource;
public AuthStorage(FastLoginCore<?, ?, ?> core, String driver, String host, int port, String databasePath
, String user, String pass, boolean useSSL) {
this.core = core;
HikariConfig config = new HikariConfig();
config.setPoolName(core.getPlugin().getName());
config.setUsername(user);
config.setPassword(pass);
config.setDriverClassName(driver);
//a try to fix https://www.spigotmc.org/threads/fastlogin.101192/page-26#post-1874647
Properties properties = new Properties();
properties.setProperty("date_string_format", "yyyy-MM-dd HH:mm:ss");
properties.setProperty("useSSL", String.valueOf(useSSL));
config.setDataSourceProperties(properties);
ThreadFactory platformThreadFactory = core.getPlugin().getThreadFactory();
if (platformThreadFactory != null) {
config.setThreadFactory(platformThreadFactory);
}
String jdbcUrl = "jdbc:";
if (driver.contains("sqlite")) {
String pluginFolder = core.getPlugin().getPluginFolder().toAbsolutePath().toString();
databasePath = databasePath.replace("{pluginDir}", pluginFolder);
jdbcUrl += "sqlite://" + databasePath;
config.setConnectionTestQuery("SELECT 1");
config.setMaximumPoolSize(1);
} else {
jdbcUrl += "mysql://" + host + ':' + port + '/' + databasePath;
}
config.setJdbcUrl(jdbcUrl);
this.dataSource = new HikariDataSource(config);
}
public void createTables() throws SQLException {
try (Connection con = dataSource.getConnection();
Statement createStmt = con.createStatement()) {
String createDataStmt = "CREATE TABLE IF NOT EXISTS " + PREMIUM_TABLE + " ("
+ "UserID INTEGER PRIMARY KEY AUTO_INCREMENT, "
+ "UUID CHAR(36), "
+ "Name VARCHAR(16) NOT NULL, "
+ "Premium BOOLEAN NOT NULL, "
+ "LastIp VARCHAR(255) NOT NULL, "
+ "LastLogin TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "
//the premium shouldn't steal the cracked account by changing the name
+ "UNIQUE (Name) "
+ ')';
if (dataSource.getJdbcUrl().contains("sqlite")) {
createDataStmt = createDataStmt.replace("AUTO_INCREMENT", "AUTOINCREMENT");
}
createStmt.executeUpdate(createDataStmt);
}
}
public StoredProfile loadProfile(String name) {
try (Connection con = dataSource.getConnection();
PreparedStatement loadStmt = con.prepareStatement(LOAD_BY_NAME)
) {
loadStmt.setString(1, name);
try (ResultSet resultSet = loadStmt.executeQuery()) {
return parseResult(resultSet).orElseGet(() -> new StoredProfile(null, name, false, ""));
}
} catch (SQLException sqlEx) {
core.getPlugin().getLog().error("Failed to query profile: {}", name, sqlEx);
}
return null;
}
public StoredProfile loadProfile(UUID uuid) {
try (Connection con = dataSource.getConnection();
PreparedStatement loadStmt = con.prepareStatement(LOAD_BY_UUID)) {
loadStmt.setString(1, UUIDAdapter.toMojangId(uuid));
try (ResultSet resultSet = loadStmt.executeQuery()) {
return parseResult(resultSet).orElse(null);
}
} catch (SQLException sqlEx) {
core.getPlugin().getLog().error("Failed to query profile: {}", uuid, sqlEx);
}
return null;
}
private Optional<StoredProfile> parseResult(ResultSet resultSet) throws SQLException {
if (resultSet.next()) {
long userId = resultSet.getInt(1);
UUID uuid = Optional.ofNullable(resultSet.getString(2)).map(UUIDAdapter::parseId).orElse(null);
String name = resultSet.getString(3);
boolean premium = resultSet.getBoolean(4);
String lastIp = resultSet.getString(5);
Instant lastLogin = resultSet.getTimestamp(6).toInstant();
return Optional.of(new StoredProfile(userId, uuid, name, premium, lastIp, lastLogin));
}
return Optional.empty();
}
public void save(StoredProfile playerProfile) {
try (Connection con = dataSource.getConnection()) {
String uuid = playerProfile.getOptId().map(UUIDAdapter::toMojangId).orElse(null);
if (playerProfile.isSaved()) {
try (PreparedStatement saveStmt = con.prepareStatement(UPDATE_PROFILE)) {
saveStmt.setString(1, uuid);
saveStmt.setString(2, playerProfile.getName());
saveStmt.setBoolean(3, playerProfile.isPremium());
saveStmt.setString(4, playerProfile.getLastIp());
saveStmt.setLong(5, playerProfile.getRowId());
saveStmt.execute();
}
} else {
try (PreparedStatement saveStmt = con.prepareStatement(INSERT_PROFILE, RETURN_GENERATED_KEYS)) {
saveStmt.setString(1, uuid);
saveStmt.setString(2, playerProfile.getName());
saveStmt.setBoolean(3, playerProfile.isPremium());
saveStmt.setString(4, playerProfile.getLastIp());
saveStmt.execute();
try (ResultSet generatedKeys = saveStmt.getGeneratedKeys()) {
if (generatedKeys != null && generatedKeys.next()) {
playerProfile.setRowId(generatedKeys.getInt(1));
}
}
}
}
} catch (SQLException ex) {
core.getPlugin().getLog().error("Failed to save playerProfile {}", playerProfile, ex);
}
}
public void close() {
dataSource.close();
}
}

View File

@@ -2,19 +2,9 @@ package com.github.games647.fastlogin.core;
public enum PremiumStatus {
PREMIUM("Premium"),
PREMIUM,
CRACKED("Cracked"),
CRACKED,
UNKNOWN("Unknown");
private final String readableName;
PremiumStatus(String readableName) {
this.readableName = readableName;
}
public String getReadableName() {
return readableName;
}
UNKNOWN
}

View File

@@ -1,62 +0,0 @@
package com.github.games647.fastlogin.core;
import com.github.games647.fastlogin.core.auth.LoginSession;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.google.common.collect.MapMaker;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
/**
* Manages player connection sessions. Login sessions that are only valid through the login process and play
* sessions that hold the stored profile object after the login process is finished and until the player leaves the
* server (Spigot) or proxy (BungeeCord).
*
* @param <C> connection object
* @param <S> platform dependent login session
*/
public abstract class SessionManager<E, C, S extends LoginSession> {
// 1 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang)
// these login sessions are only meant for during the login process not be used after
private final ConcurrentMap<C, S> loginSessions = CommonUtil.buildCache(1, 0);
private final ConcurrentMap<UUID, StoredProfile> playSessions = new MapMaker().makeMap();
public void startLoginSession(C connectionId, S session) {
loginSessions.put(connectionId, session);
}
public S getLoginSession(C connectionId) {
return loginSessions.get(connectionId);
}
public void endLoginSession(C connectionId) {
loginSessions.remove(connectionId);
}
public ConcurrentMap<C, S> getLoginSessions() {
return loginSessions;
}
public S promoteSession(C connectionId, UUID playerId) {
S loginSession = loginSessions.remove(connectionId);
StoredProfile profile = loginSession.getProfile();
playSessions.put(playerId, profile);
return loginSession;
}
public StoredProfile getPlaySession(UUID playerId) {
return playSessions.get(playerId);
}
public void endPlaySession(UUID playerId) {
playSessions.remove(playerId);
}
public abstract void onPlayQuit(E quitEvent);
public void clear() {
loginSessions.clear();
playSessions.clear();
}
}

View File

@@ -1,17 +1,16 @@
package com.github.games647.fastlogin.core.storage;
package com.github.games647.fastlogin.core;
import com.github.games647.craftapi.model.Profile;
import java.time.Instant;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.Nullable;
public class StoredProfile extends Profile {
private long rowId;
private final ReentrantLock saveLock = new ReentrantLock();
private boolean premium;
private String lastIp;
@@ -30,10 +29,6 @@ public class StoredProfile extends Profile {
this(-1, uuid, playerName, premium, lastIp, Instant.now());
}
public ReentrantLock getSaveLock() {
return saveLock;
}
public synchronized boolean isSaved() {
return rowId >= 0;
}
@@ -50,7 +45,7 @@ public class StoredProfile extends Profile {
this.rowId = generatedId;
}
// can be null
@Nullable
public synchronized UUID getId() {
return id;
}
@@ -87,21 +82,6 @@ public class StoredProfile extends Profile {
this.lastLogin = lastLogin;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof StoredProfile)) return false;
if (!super.equals(o)) return false;
StoredProfile that = (StoredProfile) o;
return rowId == that.rowId && premium == that.premium
&& Objects.equals(lastIp, that.lastIp) && lastLogin.equals(that.lastLogin);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), rowId, premium, lastIp, lastLogin);
}
@Override
public synchronized String toString() {
return this.getClass().getSimpleName() + '{' +

View File

@@ -1,77 +0,0 @@
package com.github.games647.fastlogin.core.auth;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.google.common.base.Objects;
import java.util.UUID;
public abstract class LoginSession {
private final StoredProfile profile;
private String requestUsername;
private String username;
private UUID uuid;
protected boolean registered;
public LoginSession(String requestUsername, boolean registered, StoredProfile profile) {
this.requestUsername = requestUsername;
this.username = requestUsername;
this.registered = registered;
this.profile = profile;
}
public String getRequestUsername() {
return requestUsername;
}
public String getUsername() {
return username;
}
public synchronized void setVerifiedUsername(String username) {
this.username = username;
}
/**
* @return This value is always false if we authenticate the player with a cracked authentication
*/
public synchronized boolean needsRegistration() {
return !registered;
}
public StoredProfile getProfile() {
return profile;
}
/**
* Get the premium UUID of this player
*
* @return the premium UUID or null if not fetched
*/
public synchronized UUID getUuid() {
return uuid;
}
/**
* Set the online UUID if it's fetched
*
* @param uuid premium UUID
*/
public synchronized void setUuid(UUID uuid) {
this.uuid = uuid;
}
@Override
public synchronized String toString() {
return Objects.toStringHelper(this)
.add("profile", profile)
.add("requestUsername", requestUsername)
.add("username", username)
.add("uuid", uuid)
.add("registered", registered)
.toString();
}
}

View File

@@ -1,41 +0,0 @@
package com.github.games647.fastlogin.core.auth;
/**
* Limit the number of requests with a maximum size. Each requests expires after the specified time making it available
* for another request.
*/
public class RateLimiter {
private final long[] requests;
private final long expireTime;
private int position;
public RateLimiter(int maxLimit, long expireTime) {
this.requests = new long[maxLimit];
this.expireTime = expireTime;
}
/**
* Ask if access is allowed. If so register the request.
*
* @return true if allowed - false otherwise without any side effects
*/
public boolean tryAcquire() {
// current time millis is not monotonic - it can jump back depending on user choice or NTP
long now = System.nanoTime() / 1_000_000;
// after this the request should be expired
long toBeExpired = now - expireTime;
synchronized (this) {
// having synchronized will limit the amount of concurrency a lot
long oldest = requests[position];
if (oldest < toBeExpired) {
requests[position] = now;
position = (position + 1) % requests.length;
return true;
}
return false;
}
}
}

View File

@@ -8,8 +8,6 @@ package com.github.games647.fastlogin.core.hooks;
*/
public interface AuthPlugin<P> {
String ALREADY_AUTHENTICATED = "Player {} is already authenticated. Cancelling force login.";
/**
* Login the premium (paid account) player after the player joined successfully the server.
*

View File

@@ -2,11 +2,9 @@ package com.github.games647.fastlogin.core.hooks;
import java.security.SecureRandom;
import java.util.Random;
import java.util.stream.IntStream;
public class DefaultPasswordGenerator<P> implements PasswordGenerator<P> {
private static final int PASSWORD_LENGTH = 8;
private static final char[] PASSWORD_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
.toCharArray();
@@ -15,10 +13,9 @@ public class DefaultPasswordGenerator<P> implements PasswordGenerator<P> {
@Override
public String getRandomPassword(P player) {
StringBuilder generatedPassword = new StringBuilder(8);
IntStream.rangeClosed(1, PASSWORD_LENGTH)
.map(i -> random.nextInt(PASSWORD_CHARACTERS.length - 1))
.mapToObj(pos -> PASSWORD_CHARACTERS[pos])
.forEach(generatedPassword::append);
for (int i = 1; i <= 8; i++) {
generatedPassword.append(PASSWORD_CHARACTERS[random.nextInt(PASSWORD_CHARACTERS.length - 1)]);
}
return generatedPassword.toString();
}

View File

@@ -1,26 +0,0 @@
package com.github.games647.fastlogin.core.message;
public class NamespaceKey {
private static final char SEPARATOR_CHAR = ':';
private final String namespace;
private final String key;
private final String combined;
public NamespaceKey(String namespace, String key) {
this.namespace = namespace.toLowerCase();
this.key = key.toLowerCase();
this.combined = this.namespace + SEPARATOR_CHAR + this.key;
}
public String getCombinedName() {
return combined;
}
public static String getCombined(String namespace, String key) {
return new NamespaceKey(namespace, key).combined;
}
}

View File

@@ -1,12 +1,10 @@
package com.github.games647.fastlogin.core.message;
package com.github.games647.fastlogin.core.messages;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
public class ChangePremiumMessage implements ChannelMessage {
public static final String CHANGE_CHANNEL = "ch-st";
private String playerName;
private boolean willEnable;
private boolean isSourceInvoker;
@@ -35,7 +33,7 @@ public class ChangePremiumMessage implements ChannelMessage {
@Override
public String getChannelName() {
return CHANGE_CHANNEL;
return "ChangeStatus";
}
@Override

View File

@@ -1,4 +1,4 @@
package com.github.games647.fastlogin.core.message;
package com.github.games647.fastlogin.core.messages;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;

View File

@@ -1,4 +1,4 @@
package com.github.games647.fastlogin.core.message;
package com.github.games647.fastlogin.core.messages;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
@@ -7,8 +7,6 @@ import java.util.UUID;
public class LoginActionMessage implements ChannelMessage {
public static final String FORCE_CHANNEL = "force";
private Type type;
private String playerName;
@@ -62,7 +60,7 @@ public class LoginActionMessage implements ChannelMessage {
@Override
public String getChannelName() {
return FORCE_CHANNEL;
return "LoginAction";
}
@Override

View File

@@ -1,15 +1,13 @@
package com.github.games647.fastlogin.core.message;
package com.github.games647.fastlogin.core.messages;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
public class SuccessMessage implements ChannelMessage {
public static final String SUCCESS_CHANNEL = "succ";
@Override
public String getChannelName() {
return SUCCESS_CHANNEL;
return "Success";
}
@Override

View File

@@ -2,18 +2,15 @@ package com.github.games647.fastlogin.core.shared;
import com.github.games647.craftapi.resolver.MojangResolver;
import com.github.games647.craftapi.resolver.http.RotatingProxySelector;
import com.github.games647.fastlogin.core.storage.AuthStorage;
import com.github.games647.fastlogin.core.AuthStorage;
import com.github.games647.fastlogin.core.CommonUtil;
import com.github.games647.fastlogin.core.auth.RateLimiter;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import com.github.games647.fastlogin.core.hooks.DefaultPasswordGenerator;
import com.github.games647.fastlogin.core.hooks.PasswordGenerator;
import com.google.common.net.HostAndPort;
import com.zaxxer.hikari.HikariConfig;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
@@ -24,7 +21,6 @@ import java.nio.file.Path;
import java.util.Collection;
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;
@@ -47,8 +43,6 @@ import static java.util.stream.Collectors.toSet;
*/
public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
private static final long MAX_EXPIRE_RATE = 1_000_000;
private final Map<String, String> localeMessages = new ConcurrentHashMap<>();
private final ConcurrentMap<String, Object> pendingLogin = CommonUtil.buildCache(5, -1);
private final Collection<UUID> pendingConfirms = new HashSet<>();
@@ -58,7 +52,6 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
private Configuration config;
private AuthStorage storage;
private RateLimiter rateLimiter;
private PasswordGenerator<P> passwordGenerator = new DefaultPasswordGenerator<>();
private AuthPlugin<P> authPlugin;
@@ -81,21 +74,13 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
.forEach((key, message) -> {
String colored = CommonUtil.translateColorCodes((String) message);
if (!colored.isEmpty()) {
localeMessages.put(key, colored.replace("/newline", "\n"));
localeMessages.put(key, colored);
}
});
} catch (IOException ioEx) {
plugin.getLog().error("Failed to load yaml files", ioEx);
return;
}
int maxCon = config.getInt("anti-bot.connections", 200);
long expireTime = config.getLong("anti-bot.expire", 5) * 60 * 1_000L;
if (expireTime > MAX_EXPIRE_RATE) {
expireTime = MAX_EXPIRE_RATE;
}
rateLimiter = new RateLimiter(maxCon, expireTime);
Set<Proxy> proxies = config.getStringList("proxies")
.stream()
.map(HostAndPort::fromString)
@@ -118,26 +103,15 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
}
private Configuration loadFile(String fileName) throws IOException {
ConfigurationProvider configProvider = ConfigurationProvider.getProvider(YamlConfiguration.class);
Configuration defaults;
ConfigurationProvider configProvider = ConfigurationProvider.getProvider(YamlConfiguration.class);
try (InputStream defaultStream = getClass().getClassLoader().getResourceAsStream(fileName)) {
defaults = configProvider.load(defaultStream);
}
Path file = plugin.getPluginFolder().resolve(fileName);
Configuration config;
try (Reader reader = Files.newBufferedReader(file)) {
config = configProvider.load(reader, defaults);
}
// explicitly add keys here, because Configuration.getKeys doesn't return the keys from the default configuration
for (String key : defaults.getKeys()) {
config.set(key, config.get(key));
}
return config;
return configProvider.load(Files.newBufferedReader(file), defaults);
}
public MojangResolver getResolver() {
@@ -155,7 +129,7 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
public void sendLocaleMessage(String key, C receiver) {
String message = localeMessages.get(key);
if (message != null) {
plugin.sendMultiLineMessage(receiver, message);
plugin.sendMessage(receiver, message);
}
}
@@ -164,26 +138,21 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
}
public boolean setupDatabase() {
if (!checkDriver(config.getString("driver"))) {
String driver = config.getString("driver");
if (!checkDriver(driver)) {
return false;
}
HikariConfig databaseConfig = new HikariConfig();
databaseConfig.setDriverClassName(config.getString("driver"));
String host = config.get("host", "");
int port = config.get("port", 3306);
String database = config.getString("database");
String user = config.get("username", "");
String password = config.get("password", "");
boolean useSSL = config.get("useSSL", false);
databaseConfig.setUsername(config.get("username", ""));
databaseConfig.setPassword(config.getString("password"));
databaseConfig.setConnectionTimeout(config.getInt("timeout", 30) * 1_000L);
databaseConfig.setMaxLifetime(config.getInt("lifetime", 30) * 1_000L);
storage = new AuthStorage(this, host, port, database, databaseConfig, useSSL);
storage = new AuthStorage(this, driver, host, port, database, user, password, useSSL);
try {
storage.createTables();
return true;
@@ -206,6 +175,7 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
return false;
}
public Configuration getConfig() {
return config;
}
@@ -230,10 +200,6 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
return authPlugin;
}
public RateLimiter getRateLimiter() {
return rateLimiter;
}
public void setAuthPluginHook(AuthPlugin<P> authPlugin) {
this.authPlugin = authPlugin;
}
@@ -242,12 +208,14 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
Path dataFolder = plugin.getPluginFolder();
try {
Files.createDirectories(dataFolder);
if (Files.notExists(dataFolder)) {
Files.createDirectories(dataFolder);
}
Path configFile = dataFolder.resolve(fileName);
if (Files.notExists(configFile)) {
try (InputStream defaultStream = getClass().getClassLoader().getResourceAsStream(fileName)) {
Files.copy(Objects.requireNonNull(defaultStream), configFile);
Files.copy(defaultStream, configFile);
}
}
} catch (IOException ioExc) {
@@ -256,9 +224,6 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
}
public void close() {
plugin.getLog().info("Safely shutting down scheduler. This could take up to one minute.");
plugin.getScheduler().shutdown();
if (storage != null) {
storage.close();
}

View File

@@ -1,11 +1,8 @@
package com.github.games647.fastlogin.core.auth;
package com.github.games647.fastlogin.core.shared;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.shared.PlatformPlugin;
import com.github.games647.fastlogin.core.storage.AuthStorage;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.AuthStorage;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
public abstract class ForceLoginManagement<P extends C, C, L extends LoginSession, T extends PlatformPlugin<C>>
implements Runnable {
@@ -22,17 +19,12 @@ public abstract class ForceLoginManagement<P extends C, C, L extends LoginSessio
@Override
public void run() {
if (!isOnline(player)) {
core.getPlugin().getLog().info("Player {} disconnected", player);
return;
}
if (session == null) {
core.getPlugin().getLog().info("No valid session found for {}", player);
if (!isOnline(player) || session == null) {
return;
}
AuthStorage storage = core.getStorage();
StoredProfile playerProfile = session.getProfile();
try {
if (isOnlineMode()) {
@@ -49,7 +41,7 @@ public abstract class ForceLoginManagement<P extends C, C, L extends LoginSessio
|| (core.getConfig().get("auto-register-unknown", false)
&& !authPlugin.isRegistered(playerName))) {
success = forceRegister(player);
} else if (!callFastLoginAutoLoginEvent(session, playerProfile).isCancelled()) {
} else {
success = forceLogin(player);
}
}
@@ -93,8 +85,8 @@ public abstract class ForceLoginManagement<P extends C, C, L extends LoginSessio
public boolean forceLogin(P player) {
core.getPlugin().getLog().info("Logging player {} in", getName(player));
boolean success = core.getAuthPluginHook().forceLogin(player);
if (success) {
core.sendLocaleMessage("auto-login", player);
}
@@ -102,8 +94,6 @@ public abstract class ForceLoginManagement<P extends C, C, L extends LoginSessio
return success;
}
public abstract FastLoginAutoLoginEvent callFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile);
public abstract void onForceActionSuccess(LoginSession session);
public abstract String getName(P player);

View File

@@ -1,11 +1,9 @@
package com.github.games647.fastlogin.core.auth;
package com.github.games647.fastlogin.core.shared;
import com.github.games647.craftapi.model.Profile;
import com.github.games647.craftapi.resolver.RateLimitException;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import java.util.Optional;
@@ -22,14 +20,11 @@ public abstract class JoinManagement<P extends C, C, S extends LoginSource> {
}
public void onLogin(String username, S source) {
core.getPlugin().getLog().info("Handling player {}", username);
StoredProfile profile = core.getStorage().loadProfile(username);
if (profile == null) {
return;
}
callFastLoginPreLoginEvent(username, source, profile);
Configuration config = core.getConfig();
String ip = source.getAddress().getAddress().getHostAddress();
@@ -37,7 +32,6 @@ public abstract class JoinManagement<P extends C, C, S extends LoginSource> {
try {
if (profile.isSaved()) {
if (profile.isPremium()) {
core.getPlugin().getLog().info("Requesting premium login for registered player: {}", username);
requestPremiumLogin(source, profile, username, true);
} else {
startCrackedSession(source, profile, username);
@@ -107,8 +101,6 @@ public abstract class JoinManagement<P extends C, C, S extends LoginSource> {
return false;
}
public abstract FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, S source, StoredProfile profile);
public abstract void requestPremiumLogin(S source, StoredProfile profile, String username, boolean registered);
public abstract void startCrackedSession(S source, StoredProfile profile, String username);

View File

@@ -0,0 +1,66 @@
package com.github.games647.fastlogin.core.shared;
import com.github.games647.fastlogin.core.StoredProfile;
import java.util.UUID;
public abstract class LoginSession {
private final String username;
private final StoredProfile profile;
private UUID uuid;
protected boolean registered;
public LoginSession(String username, boolean registered, StoredProfile profile) {
this.username = username;
this.registered = registered;
this.profile = profile;
}
public String getUsername() {
return username;
}
/**
* This value is always false if we authenticate the player with a cracked authentication
*
* @return
*/
public boolean needsRegistration() {
return !registered;
}
public StoredProfile getProfile() {
return profile;
}
/**
* Get the premium UUID of this player
*
* @return the premium UUID or null if not fetched
*/
public synchronized UUID getUuid() {
return uuid;
}
/**
* Set the online UUID if it's fetched
*
* @param uuid premium UUID
*/
public synchronized void setUuid(UUID uuid) {
this.uuid = uuid;
}
@Override
public synchronized String toString() {
return this.getClass().getSimpleName() + '{' +
"username='" + username + '\'' +
", profile=" + profile +
", uuid=" + uuid +
", registered=" + registered +
'}';
}
}

View File

@@ -1,4 +1,4 @@
package com.github.games647.fastlogin.core.auth;
package com.github.games647.fastlogin.core.shared;
import java.net.InetSocketAddress;

View File

@@ -1,8 +1,5 @@
package com.github.games647.fastlogin.core.shared;
import com.github.games647.fastlogin.core.AsyncScheduler;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.nio.file.Path;
import java.util.concurrent.ThreadFactory;
@@ -18,20 +15,7 @@ public interface PlatformPlugin<C> {
void sendMessage(C receiver, String message);
AsyncScheduler getScheduler();
default void sendMultiLineMessage(C receiver, String message) {
for (String line : message.split("%nl%")) {
sendMessage(receiver, line);
}
}
default ThreadFactory getThreadFactory() {
return new ThreadFactoryBuilder()
.setNameFormat(getName() + " Pool Thread #%1$d")
// Hikari create daemons by default and we could daemon threads for our own scheduler too
// because we safely shutdown
.setDaemon(true)
.build();
return null;
}
}

View File

@@ -1,10 +0,0 @@
package com.github.games647.fastlogin.core.shared.event;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.LoginSession;
public interface FastLoginAutoLoginEvent extends FastLoginCancellableEvent {
LoginSession getSession();
StoredProfile getProfile();
}

View File

@@ -1,8 +0,0 @@
package com.github.games647.fastlogin.core.shared.event;
public interface FastLoginCancellableEvent {
boolean isCancelled();
void setCancelled(boolean cancelled);
}

View File

@@ -1,13 +0,0 @@
package com.github.games647.fastlogin.core.shared.event;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.LoginSource;
public interface FastLoginPreLoginEvent {
String getUsername();
LoginSource getSource();
StoredProfile getProfile();
}

View File

@@ -1,15 +0,0 @@
package com.github.games647.fastlogin.core.shared.event;
import com.github.games647.fastlogin.core.storage.StoredProfile;
public interface FastLoginPremiumToggleEvent {
StoredProfile getProfile();
PremiumToggleReason getReason();
enum PremiumToggleReason {
COMMAND_SELF,
COMMAND_OTHER
}
}

View File

@@ -1,226 +0,0 @@
package com.github.games647.fastlogin.core.storage;
import com.github.games647.craftapi.UUIDAdapter;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.Instant;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ThreadFactory;
import static java.sql.Statement.RETURN_GENERATED_KEYS;
public class AuthStorage {
private static final String PREMIUM_TABLE = "premium";
private static final String LOAD_BY_NAME = "SELECT * FROM `" + PREMIUM_TABLE + "` WHERE `Name`=? LIMIT 1";
private static final String LOAD_BY_UUID = "SELECT * FROM `" + PREMIUM_TABLE + "` WHERE `UUID`=? LIMIT 1";
private static final String INSERT_PROFILE = "INSERT INTO `" + PREMIUM_TABLE
+ "` (`UUID`, `Name`, `Premium`, `LastIp`) " + "VALUES (?, ?, ?, ?) ";
// limit not necessary here, because it's unique
private static final String UPDATE_PROFILE = "UPDATE `" + PREMIUM_TABLE
+ "` SET `UUID`=?, `Name`=?, `Premium`=?, `LastIp`=?, `LastLogin`=CURRENT_TIMESTAMP WHERE `UserID`=?";
private final FastLoginCore<?, ?, ?> core;
private final HikariDataSource dataSource;
public AuthStorage(FastLoginCore<?, ?, ?> core, String host, int port, String databasePath,
HikariConfig config, boolean useSSL) {
this.core = core;
config.setPoolName(core.getPlugin().getName());
ThreadFactory platformThreadFactory = core.getPlugin().getThreadFactory();
if (platformThreadFactory != null) {
config.setThreadFactory(platformThreadFactory);
}
String jdbcUrl = "jdbc:";
if (config.getDriverClassName().contains("sqlite")) {
String pluginFolder = core.getPlugin().getPluginFolder().toAbsolutePath().toString();
databasePath = databasePath.replace("{pluginDir}", pluginFolder);
jdbcUrl += "sqlite://" + databasePath;
config.setConnectionTestQuery("SELECT 1");
config.setMaximumPoolSize(1);
//a try to fix https://www.spigotmc.org/threads/fastlogin.101192/page-26#post-1874647
// format strings retrieved by the timestamp column to match them from MySQL
config.addDataSourceProperty("date_string_format", "yyyy-MM-dd HH:mm:ss");
// TODO: test first for compatibility
// config.addDataSourceProperty("date_precision", "seconds");
} else {
jdbcUrl += "mysql://" + host + ':' + port + '/' + databasePath;
// Require SSL on the server if requested in config - this will also verify certificate
// Those values are deprecated in favor of sslMode
config.addDataSourceProperty("useSSL", useSSL);
config.addDataSourceProperty("requireSSL", useSSL);
// prefer encrypted if possible
config.addDataSourceProperty("sslMode", "PREFERRED");
// adding paranoid hides hostname, username, version and so
// could be useful for hiding server details
config.addDataSourceProperty("paranoid", true);
// enable MySQL specific optimizations
// disabled by default - will return the same prepared statement instance
config.addDataSourceProperty("cachePrepStmts", true);
// default prepStmtCacheSize 25 - amount of cached statements
config.addDataSourceProperty("prepStmtCacheSize", 250);
// default prepStmtCacheSqlLimit 256 - length of SQL
config.addDataSourceProperty("prepStmtCacheSqlLimit", 2048);
// default false - available in newer versions caches the statements server-side
config.addDataSourceProperty("useServerPrepStmts", true);
// default false - prefer use of local values for autocommit and
// transaction isolation (alwaysSendSetIsolation) should only be enabled if always use the set* methods
// instead of raw SQL
// https://forums.mysql.com/read.php?39,626495,626512
config.addDataSourceProperty("useLocalSessionState", true);
// rewrite batched statements to a single statement, adding them behind each other
// only useful for addBatch statements and inserts
config.addDataSourceProperty("rewriteBatchedStatements", true);
// cache result metadata
config.addDataSourceProperty("cacheResultSetMetadata", true);
// cache results of show variables and collation per URL
config.addDataSourceProperty("cacheServerConfiguration", true);
// default false - set auto commit only if not matching
config.addDataSourceProperty("elideSetAutoCommits", true);
// default true - internal timers for idle calculation -> removes System.getCurrentTimeMillis call per query
// Some platforms are slow on this and it could affect the throughput about 3% according to MySQL
// performance gems presentation
// In our case it can be useful to see the time in error messages
// config.addDataSourceProperty("maintainTimeStats", false);
}
config.setJdbcUrl(jdbcUrl);
this.dataSource = new HikariDataSource(config);
}
public void createTables() throws SQLException {
// choose surrogate PK(ID), because UUID can be null for offline players
// if UUID is always Premium UUID we would have to update offline player entries on insert
// name cannot be PK, because it can be changed for premium players
String createDataStmt = "CREATE TABLE IF NOT EXISTS `" + PREMIUM_TABLE + "` ("
+ "`UserID` INTEGER PRIMARY KEY AUTO_INCREMENT, "
+ "`UUID` CHAR(36), "
+ "`Name` VARCHAR(16) NOT NULL, "
+ "`Premium` BOOLEAN NOT NULL, "
+ "`LastIp` VARCHAR(255) NOT NULL, "
+ "`LastLogin` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "
//the premium shouldn't steal the cracked account by changing the name
+ "UNIQUE (`Name`) "
+ ')';
if (dataSource.getJdbcUrl().contains("sqlite")) {
createDataStmt = createDataStmt.replace("AUTO_INCREMENT", "AUTOINCREMENT");
}
//todo: add unique uuid index usage
try (Connection con = dataSource.getConnection();
Statement createStmt = con.createStatement()) {
createStmt.executeUpdate(createDataStmt);
}
}
public StoredProfile loadProfile(String name) {
try (Connection con = dataSource.getConnection();
PreparedStatement loadStmt = con.prepareStatement(LOAD_BY_NAME)
) {
loadStmt.setString(1, name);
try (ResultSet resultSet = loadStmt.executeQuery()) {
return parseResult(resultSet).orElseGet(() -> new StoredProfile(null, name, false, ""));
}
} catch (SQLException sqlEx) {
core.getPlugin().getLog().error("Failed to query profile: {}", name, sqlEx);
}
return null;
}
public StoredProfile loadProfile(UUID uuid) {
try (Connection con = dataSource.getConnection();
PreparedStatement loadStmt = con.prepareStatement(LOAD_BY_UUID)) {
loadStmt.setString(1, UUIDAdapter.toMojangId(uuid));
try (ResultSet resultSet = loadStmt.executeQuery()) {
return parseResult(resultSet).orElse(null);
}
} catch (SQLException sqlEx) {
core.getPlugin().getLog().error("Failed to query profile: {}", uuid, sqlEx);
}
return null;
}
private Optional<StoredProfile> parseResult(ResultSet resultSet) throws SQLException {
if (resultSet.next()) {
long userId = resultSet.getInt(1);
UUID uuid = Optional.ofNullable(resultSet.getString(2)).map(UUIDAdapter::parseId).orElse(null);
String name = resultSet.getString(3);
boolean premium = resultSet.getBoolean(4);
String lastIp = resultSet.getString(5);
Instant lastLogin = resultSet.getTimestamp(6).toInstant();
return Optional.of(new StoredProfile(userId, uuid, name, premium, lastIp, lastLogin));
}
return Optional.empty();
}
public void save(StoredProfile playerProfile) {
try (Connection con = dataSource.getConnection()) {
String uuid = playerProfile.getOptId().map(UUIDAdapter::toMojangId).orElse(null);
playerProfile.getSaveLock().lock();
try {
if (playerProfile.isSaved()) {
try (PreparedStatement saveStmt = con.prepareStatement(UPDATE_PROFILE)) {
saveStmt.setString(1, uuid);
saveStmt.setString(2, playerProfile.getName());
saveStmt.setBoolean(3, playerProfile.isPremium());
saveStmt.setString(4, playerProfile.getLastIp());
saveStmt.setLong(5, playerProfile.getRowId());
saveStmt.execute();
}
} else {
try (PreparedStatement saveStmt = con.prepareStatement(INSERT_PROFILE, RETURN_GENERATED_KEYS)) {
saveStmt.setString(1, uuid);
saveStmt.setString(2, playerProfile.getName());
saveStmt.setBoolean(3, playerProfile.isPremium());
saveStmt.setString(4, playerProfile.getLastIp());
saveStmt.execute();
try (ResultSet generatedKeys = saveStmt.getGeneratedKeys()) {
if (generatedKeys.next()) {
playerProfile.setRowId(generatedKeys.getInt(1));
}
}
}
}
} finally {
playerProfile.getSaveLock().unlock();
}
} catch (SQLException ex) {
core.getPlugin().getLog().error("Failed to save playerProfile {}", playerProfile, ex);
}
}
public void close() {
dataSource.close();
}
}

View File

@@ -3,22 +3,7 @@
# Source code: https://github.com/games647/FastLogin
#
# You can access the newest config here:
# https://github.com/games647/FastLogin/blob/main/core/src/main/resources/config.yml
# This a **very** simple anti bot protection. Recommendation is to use a a dedicated program to approach this
# problem. Low level firewalls like uwf (or iptables direct) are more efficient than a Minecraft plugin. TCP reverse
# proxies could also be used and offload some work even to different host.
#
# The settings wil limit how many connections this plugin will handle. After hitting this limit. FastLogin will
# completely ignore incoming connections. Effectively there will be no database requests and network requests.
# Therefore auto logins won't be possible.
anti-bot:
# Image the following like bucket. The following is total amount that is allowed in this bucket, while expire
# means how long it takes for every entry to expire.
# Total number of connections
connections: 200
# Amount of minutes after the first connection will expire and made available
expire: 5
# https://github.com/games647/FastLogin/blob/master/core/src/main/resources/config.yml
# Request a premium login without forcing the player to type a command
#
@@ -36,15 +21,6 @@ anti-bot:
# For more information: https://github.com/games647/FastLogin#why-do-players-have-to-invoke-a-command
autoRegister: false
# Should FastLogin respect per IP limit of registrations (e.g. in AuthMe)
# Because most auth plugins do their stuff async - FastLogin will still think the player was registered
# To work best - you also need to enable auto-register-unknown
#
# If set to true - FastLogin will always attempt to register the player, even if the limit is exceeded
# It is up to the auth plugin to handle the excessive registration
# https://github.com/games647/FastLogin/issues/458
respectIpLimit: false
# This is extra configuration option to the feature above. If we request a premium authentication from a player who
# isn't actual premium but used a premium username, the player will disconnect with the reason "invalid session" or
# "bad login".
@@ -56,8 +32,7 @@ secondAttemptCracked: false
# New cracked players will be kicked from server. Good if you want switch from offline-mode to online-mode without
# losing players!
#
# Existing cracked and premium players could still join your server. Moreover you could add playernames to a
# allowlist.
# Existing cracked and premium players could still join your server. Moreover you could add playernames to a whitelist.
# So that these cracked players could join too although they are new players.
switchMode: false
@@ -85,15 +60,14 @@ premiumUuid: false
# player changed it's username and we just update the name in the database.
# Examples:
# #### Case 1
# autoRegister = false
# nameChangeCheck = false
# nameChangeCheck = false ----- autoRegister = false
#
# GameProfile logins as cracked until the player invoked the command /premium. Then we could override the existing
# database record.
#
# #### Case 2
# autoRegister = false
# nameChangeCheck = true
#
# nameChangeCheck = true ----- autoRegister = false
#
# Connect the Mojang API and check what UUID the player has (UUID exists => Paid Minecraft account). If that UUID is in
# the database it's an **existing player** and FastLogin can **assume** the player is premium and changed the username.
@@ -105,8 +79,8 @@ premiumUuid: false
# in the meanwhile).
#
# #### Case 3
# autoRegister = true
# nameChangeCheck = false
#
# nameChangeCheck = false ----- autoRegister = true
#
# We will always request a premium authentication if the username is unknown to us, but is in use by a paid Minecraft
# account. This means it's kind of a more aggressive check like nameChangeCheck = true and autoRegister = false, because
@@ -115,8 +89,8 @@ premiumUuid: false
# **Limitation**: see below
#
# #### Case 4
# autoRegister = true
# nameChangeCheck = true
#
# nameChangeCheck = true ----- autoRegister = true
#
# Based on autoRegister it checks if the player name is premium and login using a premium authentication. After that
# fastlogin receives the premium UUID and can update the database record.
@@ -137,10 +111,6 @@ nameChangeCheck: false
# the skin data is included in the Auth-Verification-Response sent by Mojang. If you want to use for other
# players like cracked player, you have to use other plugins.
#
# If you use PaperSpigot - FastLogin will always try to set the skin, even if forwardSkin is set to false
# It is needed to allow premium name change to work correctly
# https://github.com/games647/FastLogin/issues/457
#
# If you want to use skins for your cracked player, you need an additional plugin like
# ChangeSkin, SkinRestorer, ...
forwardSkin: true
@@ -191,22 +161,17 @@ autoLogin: true
# Recommended is the use of MariaDB (a better version of MySQL)
# Single file SQLite database
driver: 'org.sqlite.JDBC'
driver: org.sqlite.JDBC
# File location
database: '{pluginDir}/FastLogin.db'
# MySQL/MariaDB
# If you want to enable it uncomment only the lines below this not this line.
#driver: 'com.mysql.jdbc.Driver'
#host: '127.0.0.1'
#driver: com.mysql.jdbc.Driver
#host: 127.0.0.1
#port: 3306
#database: 'fastlogin'
#username: 'myUser'
#password: 'myPassword'
# Advanced Connection Pool settings in seconds
#timeout: 30
#lifetime: 30
#database: fastlogin
#username: myUser
#password: myPassword
# It's strongly recommended to enable SSL and setup a SSL certificate if the MySQL server isn't running on the same
# machine

View File

@@ -3,7 +3,7 @@
# Source code: https://github.com/games647/FastLogin
#
# You can access the newest locale here:
# https://github.com/games647/FastLogin/blob/main/core/src/main/resources/messages.yml
# https://github.com/games647/FastLogin/blob/master/core/src/main/resources/messages.yml
#
# You want to have language template? Visit the GitHub Wiki here:
# https://github.com/games647/FastLogin/wiki/English
@@ -21,8 +21,8 @@
# ========= Shared (BungeeCord and Bukkit) ============
# Switch mode is activated and a new cracked player tries to join, who is not namely allowed
switch-kick-message: '&4Only paid Minecraft allowed accounts are allowed to join this server'
# Switch mode is activated and a new (non-whitelist) cracked player tries to join
switch-kick-message: '&4Only paid Minecraft whitelisted accounts are allowed to join this server'
# GameProfile activated premium login in order to skip offline authentication
add-premium: '&2Added to the list of premium players'
@@ -53,10 +53,10 @@ player-unknown: '&4Player not in the database'
# The user skipped the authentication, because it was a premium player
auto-login: '&2Auto logged in'
# FastLogin attempted to auto register user. The user account is registered to protect it from cracked players
# If FastLogin is respecting auth plugin IP limit - the registration may have failed, however the message is still displayed
# The user was auto registered on the first join. The user account will be registered to protect it from cracked players
# The password can be used if the mojang servers are down and you still want your premium users to login (PLANNED)
auto-register: '&2Tried auto registering with password: &7%password&2. You may want change it?'
auto-register: '&2Auto registered with password: %password
You may want change it?'
# GameProfile is not able to toggle the premium state of other players
no-permission: '&4Not enough permissions'

View File

@@ -1,69 +0,0 @@
package com.github.games647.fastlogin.core;
import com.github.games647.fastlogin.core.auth.RateLimiter;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class RateLimiterTest {
private static final long THRESHOLD_MILLI = 10;
/**
* Always expired
*/
@Test
public void allowExpire() throws InterruptedException {
int size = 3;
// run twice the size to fill it first and then test it
RateLimiter rateLimiter = new RateLimiter(size, 0);
for (int i = 0; i < size; i++) {
assertTrue("Filling up", rateLimiter.tryAcquire());
}
for (int i = 0; i < size; i++) {
Thread.sleep(1);
assertTrue("Should be expired", rateLimiter.tryAcquire());
}
}
/**
* Too many requests
*/
@Test
public void shoudBlock() {
int size = 3;
// fill the size
RateLimiter rateLimiter = new RateLimiter(size, TimeUnit.SECONDS.toMillis(30));
for (int i = 0; i < size; i++) {
assertTrue("Filling up", rateLimiter.tryAcquire());
}
assertFalse("Should be full and no entry should be expired", rateLimiter.tryAcquire());
}
/**
* Blocked attempts shouldn't replace existing ones.
*/
@Test
public void blockedNotAdded() throws InterruptedException {
// fill the size - 100ms should be reasonable high
RateLimiter rateLimiter = new RateLimiter(1, 100);
assertTrue("Filling up", rateLimiter.tryAcquire());
Thread.sleep(50);
// still is full - should fail
assertFalse("Expired too early", rateLimiter.tryAcquire());
// wait the remaining time and add a threshold, because
Thread.sleep(50 + THRESHOLD_MILLI);
assertTrue("Request not released", rateLimiter.tryAcquire());
}
}

34
pom.xml
View File

@@ -1,5 +1,5 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.games647</groupId>
@@ -8,11 +8,11 @@
<packaging>pom</packaging>
<name>FastLogin</name>
<version>1.11-SNAPSHOT</version>
<version>1.11</version>
<url>https://www.spigotmc.org/resources/fastlogin.14153/</url>
<description>
Automatically login premium (paid accounts) player on a offline mode server
Automatically logins premium (paid accounts) player on a offline mode server
</description>
<properties>
@@ -21,9 +21,8 @@
<!-- Set default for non-git clones -->
<git.commit.id>Unknown</git.commit.id>
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<modules>
@@ -32,18 +31,6 @@
<module>bungee</module>
</modules>
<!--Deployment configuration for the Maven repository-->
<distributionManagement>
<snapshotRepository>
<id>codemc-snapshots</id>
<url>https://repo.codemc.io/repository/maven-snapshots/</url>
</snapshotRepository>
<repository>
<id>codemc-releases</id>
<url>https://repo.codemc.io/repository/maven-releases/</url>
</repository>
</distributionManagement>
<build>
<!--Just use the project name to replace an old version of the plugin if the user does only copy-paste-->
<finalName>${project.name}</finalName>
@@ -52,7 +39,7 @@
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<version>4.0.3</version>
<version>2.2.4</version>
<configuration>
<failOnNoGitDirectory>false</failOnNoGitDirectory>
</configuration>
@@ -75,13 +62,4 @@
</resource>
</resources>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>