mirror of
https://github.com/TuxCoding/FastLogin.git
synced 2025-12-23 15:18:09 +01:00
Compare commits
6 Commits
dependency
...
confirm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5676e99dec | ||
|
|
65469ed579 | ||
|
|
8ecb5657d3 | ||
|
|
b0cf6e39c7 | ||
|
|
88a526b5bf | ||
|
|
57b84509da |
@@ -51,7 +51,7 @@ https://ci.codemc.org/job/Games647/job/FastLogin/changes
|
||||
* 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.10+
|
||||
* [Spigot](https://www.spigotmc.org) 1.8.8+
|
||||
* Java 8+
|
||||
* Run Spigot and/or BungeeCord/Waterfall in offline mode (see server.properties or config.yml)
|
||||
* An auth plugin. Supported plugins
|
||||
|
||||
@@ -30,6 +30,14 @@ public class BukkitLoginSession extends LoginSession {
|
||||
this.verifyToken = verifyToken.clone();
|
||||
}
|
||||
|
||||
public BukkitLoginSession(String username, String serverId, byte[] verifyToken, StoredProfile profile) {
|
||||
// confirmation login
|
||||
super(username, profile);
|
||||
|
||||
this.serverId = serverId;
|
||||
this.verifyToken = verifyToken.clone();
|
||||
}
|
||||
|
||||
//available for BungeeCord
|
||||
public BukkitLoginSession(String username, boolean registered) {
|
||||
this(username, "", EMPTY_ARRAY, registered, null);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -41,17 +41,24 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
|
||||
|
||||
//1 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang)
|
||||
private final ConcurrentMap<String, BukkitLoginSession> loginSession = CommonUtil.buildCache(1, -1);
|
||||
private final Logger logger = CommonUtil.createLoggerFromJDK(getLogger());
|
||||
private final Map<UUID, PremiumStatus> premiumPlayers = new ConcurrentHashMap<>();
|
||||
private final Logger logger;
|
||||
|
||||
private boolean serverStarted;
|
||||
private boolean bungeeCord;
|
||||
private final BukkitScheduler scheduler;
|
||||
private FastLoginCore<Player, CommandSender, FastLoginBukkit> core;
|
||||
|
||||
public FastLoginBukkit() {
|
||||
this.logger = CommonUtil.createLoggerFromJDK(getLogger());
|
||||
this.scheduler = new BukkitScheduler(this, logger, getThreadFactory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
core = new FastLoginCore<>(this);
|
||||
core.load();
|
||||
|
||||
try {
|
||||
bungeeCord = Class.forName("org.spigotmc.SpigotConfig").getDeclaredField("bungee").getBoolean(null);
|
||||
} catch (ClassNotFoundException notFoundEx) {
|
||||
@@ -72,7 +79,7 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
|
||||
setServerStarted();
|
||||
|
||||
// check for incoming messages from the bungeecord version of this plugin
|
||||
String forceChannel = new NamespaceKey(getName(), LoginActionMessage.FORCE_CHANNEL).getCombinedName();
|
||||
String forceChannel = NamespaceKey.getCombined(getName(), LoginActionMessage.FORCE_CHANNEL);
|
||||
getServer().getMessenger().registerIncomingPluginChannel(this, forceChannel, new BungeeListener(this));
|
||||
|
||||
// outgoing
|
||||
@@ -195,6 +202,11 @@ 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);
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.github.games647.fastlogin.bukkit.command;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
@@ -44,7 +43,7 @@ public class CrackedCommand extends ToggleCommand {
|
||||
|
||||
profile.setPremium(false);
|
||||
profile.setId(null);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
plugin.getScheduler().runAsync(() -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
});
|
||||
} else {
|
||||
@@ -76,7 +75,7 @@ public class CrackedCommand extends ToggleCommand {
|
||||
plugin.getCore().sendLocaleMessage("remove-premium", sender);
|
||||
|
||||
profile.setPremium(false);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
plugin.getScheduler().runAsync(() -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package com.github.games647.fastlogin.bukkit.command;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.ConfirmationState;
|
||||
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;
|
||||
@@ -37,31 +35,44 @@ public class PremiumCommand extends ToggleCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
if (forwardPremiumCommand(sender, sender.getName())) {
|
||||
Player player = (Player) sender;
|
||||
String playerName = sender.getName();
|
||||
if (forwardPremiumCommand(sender, playerName)) {
|
||||
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;
|
||||
// non-bungee mode
|
||||
if (plugin.getConfig().getBoolean("premium-confirm")) {
|
||||
ConfirmationState state = plugin.getCore().getPendingConfirms().get(playerName);
|
||||
if (state == null) {
|
||||
// no pending confirmation
|
||||
plugin.getCore().getPendingConfirms().put(playerName, ConfirmationState.REQUIRE_RELOGIN);
|
||||
player.kickPlayer(plugin.getCore().getMessage("premium-confirm"));
|
||||
} else if (state == ConfirmationState.REQUIRE_AUTH_PLUGIN_LOGIN) {
|
||||
// player logged in successful using premium authentication
|
||||
activate(sender, playerName);
|
||||
}
|
||||
} else {
|
||||
activate(sender, playerName);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
private void activate(CommandSender sender, String playerName) {
|
||||
plugin.getCore().getPendingConfirms().remove(playerName);
|
||||
|
||||
plugin.getCore().sendLocaleMessage("add-premium", sender);
|
||||
}
|
||||
//todo: load async
|
||||
StoredProfile profile = plugin.getCore().getStorage().loadProfile(playerName);
|
||||
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.getCore().sendLocaleMessage("add-premium", sender);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPremiumOther(CommandSender sender, Command command, String[] args) {
|
||||
@@ -85,7 +96,7 @@ public class PremiumCommand extends ToggleCommand {
|
||||
} else {
|
||||
//todo: resolve uuid
|
||||
profile.setPremium(true);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
plugin.getScheduler().runAsync(() -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
});
|
||||
|
||||
|
||||
@@ -51,7 +51,6 @@ public class ConnectionListener implements Listener {
|
||||
Player player = quitEvent.getPlayer();
|
||||
removeBlacklistStatus(player);
|
||||
|
||||
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
|
||||
plugin.getPremiumPlayers().remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.JoinManagement;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.util.Random;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@@ -81,6 +81,26 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestConfirmationLogin(ProtocolLibLoginSource source, StoredProfile profile, String username) {
|
||||
try {
|
||||
source.setOnlineMode();
|
||||
} catch (Exception ex) {
|
||||
plugin.getLog().error("Cannot send encryption packet. Falling back to cracked login for: {}", profile, ex);
|
||||
return;
|
||||
}
|
||||
|
||||
String serverId = source.getServerId();
|
||||
byte[] verify = source.getVerifyToken();
|
||||
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(username, serverId, verify, profile);
|
||||
plugin.getLoginSessions().put(player.getAddress().toString(), 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);
|
||||
|
||||
@@ -11,7 +11,6 @@ import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import java.security.KeyPair;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import static com.comphenix.protocol.PacketType.Login.Client.ENCRYPTION_BEGIN;
|
||||
@@ -67,7 +66,7 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
|
||||
packetEvent.getAsyncMarker().incrementProcessingDelay();
|
||||
Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, sharedSecret, keyPair);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, verifyTask);
|
||||
plugin.getScheduler().runAsync(verifyTask);
|
||||
}
|
||||
|
||||
private void onLogin(PacketEvent packetEvent, Player player) {
|
||||
@@ -85,6 +84,6 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
|
||||
packetEvent.getAsyncMarker().incrementProcessingDelay();
|
||||
Runnable nameCheckTask = new NameCheckTask(plugin, packetEvent, random, player, username, keyPair.getPublic());
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, nameCheckTask);
|
||||
plugin.getScheduler().runAsync(nameCheckTask);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,10 +29,6 @@ public class ProtocolLoginSource implements LoginSource {
|
||||
return loginStartEvent.getAddress();
|
||||
}
|
||||
|
||||
public PlayerLoginStartEvent getLoginStartEvent() {
|
||||
return loginStartEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
|
||||
@@ -6,11 +6,11 @@ import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.JoinManagement;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
@@ -84,9 +84,14 @@ public class ProtocolSupportListener extends JoinManagement<Player, CommandSende
|
||||
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(username, registered, profile);
|
||||
plugin.getLoginSessions().put(source.getAddress().toString(), playerSession);
|
||||
if (plugin.getConfig().getBoolean("premiumUuid")) {
|
||||
source.getLoginStartEvent().setOnlineMode(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestConfirmationLogin(ProtocolLoginSource source, StoredProfile profile, String username) {
|
||||
source.setOnlineMode();
|
||||
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(username, profile);
|
||||
plugin.getLoginSessions().put(source.getAddress().toString(), playerSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -12,6 +12,10 @@ public class BungeeLoginSession extends LoginSession {
|
||||
super(username, registered, profile);
|
||||
}
|
||||
|
||||
public BungeeLoginSession(String username, StoredProfile profile) {
|
||||
super(username, profile);
|
||||
}
|
||||
|
||||
public synchronized void setRegistered(boolean registered) {
|
||||
this.registered = registered;
|
||||
}
|
||||
@@ -22,6 +26,7 @@ public class BungeeLoginSession extends LoginSession {
|
||||
|
||||
public synchronized void setAlreadySaved(boolean alreadySaved) {
|
||||
this.alreadySaved = alreadySaved;
|
||||
this.confirmationLogin = false;
|
||||
}
|
||||
|
||||
public synchronized boolean isAlreadyLogged() {
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.github.games647.fastlogin.bungee;
|
||||
import com.github.games647.fastlogin.bungee.hook.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.core.CommonUtil;
|
||||
import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
|
||||
import com.github.games647.fastlogin.core.message.ChannelMessage;
|
||||
@@ -37,11 +38,13 @@ public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSen
|
||||
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();
|
||||
@@ -54,8 +57,8 @@ public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSen
|
||||
getProxy().getPluginManager().registerListener(this, new PluginMessageListener(this));
|
||||
|
||||
//this is required to listen to incoming messages from the server
|
||||
getProxy().registerChannel(new NamespaceKey(getName(), ChangePremiumMessage.CHANGE_CHANNEL).getCombinedName());
|
||||
getProxy().registerChannel(new NamespaceKey(getName(), SuccessMessage.SUCCESS_CHANNEL).getCombinedName());
|
||||
getProxy().registerChannel(NamespaceKey.getCombined(getName(), ChangePremiumMessage.CHANGE_CHANNEL));
|
||||
getProxy().registerChannel(NamespaceKey.getCombined(getName(), SuccessMessage.SUCCESS_CHANNEL));
|
||||
|
||||
registerHook();
|
||||
}
|
||||
@@ -117,10 +120,15 @@ public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSen
|
||||
@SuppressWarnings("deprecation")
|
||||
public ThreadFactory getThreadFactory() {
|
||||
return new ThreadFactoryBuilder()
|
||||
.setNameFormat(getName() + " Database Pool Thread #%1$d")
|
||||
.setNameFormat(getName() + " Pool Thread #%1$d")
|
||||
//Hikari create daemons by default
|
||||
.setDaemon(true)
|
||||
.setThreadFactory(new GroupedThreadFactory(this, getName()))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ import com.github.games647.craftapi.UUIDAdapter;
|
||||
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.ConfirmationState;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
|
||||
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;
|
||||
@@ -48,7 +48,7 @@ public class ConnectListener implements Listener {
|
||||
|
||||
PendingConnection connection = preLoginEvent.getConnection();
|
||||
Runnable asyncPremiumCheck = new AsyncPremiumCheck(plugin, preLoginEvent, connection);
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, asyncPremiumCheck);
|
||||
plugin.getScheduler().runAsync(asyncPremiumCheck);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
@@ -101,13 +101,13 @@ public class ConnectListener implements Listener {
|
||||
Server server = serverConnectedEvent.getServer();
|
||||
|
||||
Runnable loginTask = new ForceLoginTask(plugin.getCore(), player, server);
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, loginTask);
|
||||
plugin.getScheduler().runAsync(loginTask);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onDisconnect(PlayerDisconnectEvent disconnectEvent) {
|
||||
ProxiedPlayer player = disconnectEvent.getPlayer();
|
||||
plugin.getSession().remove(player.getPendingConnection());
|
||||
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
|
||||
plugin.getCore().getPendingConfirms().remove(player.getName(), ConfirmationState.REQUIRE_AUTH_PLUGIN_LOGIN);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ 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.ConfirmationState;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
|
||||
import com.github.games647.fastlogin.core.message.NamespaceKey;
|
||||
@@ -14,8 +15,6 @@ 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;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
@@ -56,40 +55,57 @@ public class PluginMessageListener implements Listener {
|
||||
byte[] data = Arrays.copyOf(pluginMessageEvent.getData(), pluginMessageEvent.getData().length);
|
||||
ProxiedPlayer forPlayer = (ProxiedPlayer) pluginMessageEvent.getReceiver();
|
||||
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> readMessage(forPlayer, channel, data));
|
||||
plugin.getScheduler().runAsync(() -> readMessage(forPlayer, channel, data));
|
||||
}
|
||||
|
||||
private void readMessage(ProxiedPlayer forPlayer, String channel, byte[] data) {
|
||||
FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core = plugin.getCore();
|
||||
|
||||
private void readMessage(ProxiedPlayer fromPlayer, String channel, byte[] data) {
|
||||
ByteArrayDataInput dataInput = ByteStreams.newDataInput(data);
|
||||
if (successChannel.equals(channel)) {
|
||||
onSuccessMessage(forPlayer);
|
||||
onSuccessMessage(fromPlayer);
|
||||
} else if (changeChannel.equals(channel)) {
|
||||
ChangePremiumMessage changeMessage = new ChangePremiumMessage();
|
||||
changeMessage.readFrom(dataInput);
|
||||
|
||||
String playerName = changeMessage.getPlayerName();
|
||||
boolean isSourceInvoker = changeMessage.isSourceInvoker();
|
||||
if (changeMessage.shouldEnable()) {
|
||||
if (playerName.equals(forPlayer.getName()) && plugin.getCore().getConfig().get("premium-warning", true)
|
||||
&& !core.getPendingConfirms().contains(forPlayer.getUniqueId())) {
|
||||
String message = core.getMessage("premium-warning");
|
||||
forPlayer.sendMessage(TextComponent.fromLegacyText(message));
|
||||
core.getPendingConfirms().add(forPlayer.getUniqueId());
|
||||
return;
|
||||
}
|
||||
|
||||
core.getPendingConfirms().remove(forPlayer.getUniqueId());
|
||||
Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, true, isSourceInvoker);
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, task);
|
||||
} else {
|
||||
Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, false, isSourceInvoker);
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, task);
|
||||
}
|
||||
onChangeMessage(fromPlayer, changeMessage.shouldEnable(), playerName, isSourceInvoker);
|
||||
}
|
||||
}
|
||||
|
||||
private void onChangeMessage(ProxiedPlayer fromPlayer, boolean shouldEnable, String playerName, boolean isSourceInvoker) {
|
||||
FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core = plugin.getCore();
|
||||
if (shouldEnable) {
|
||||
if (!isSourceInvoker) {
|
||||
// fromPlayer is not the target player
|
||||
activePremiumLogin(fromPlayer, playerName, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (plugin.getCore().getConfig().getBoolean("premium-confirm", true)) {
|
||||
ConfirmationState state = plugin.getCore().getPendingConfirms().get(playerName);
|
||||
if (state == null) {
|
||||
// no pending confirmation
|
||||
core.sendLocaleMessage("premium-confirm", fromPlayer);
|
||||
core.getPendingConfirms().put(playerName, ConfirmationState.REQUIRE_RELOGIN);
|
||||
} else if (state == ConfirmationState.REQUIRE_AUTH_PLUGIN_LOGIN) {
|
||||
// player logged in successful using premium authentication
|
||||
activePremiumLogin(fromPlayer, playerName, true);
|
||||
}
|
||||
} else {
|
||||
activePremiumLogin(fromPlayer, playerName, true);
|
||||
}
|
||||
} else {
|
||||
Runnable task = new AsyncToggleMessage(core, fromPlayer, playerName, false, isSourceInvoker);
|
||||
plugin.getScheduler().runAsync(task);
|
||||
}
|
||||
}
|
||||
|
||||
private void activePremiumLogin(ProxiedPlayer fromPlayer, String playerName, boolean isSourceInvoker) {
|
||||
plugin.getCore().getPendingConfirms().remove(playerName);
|
||||
Runnable task = new AsyncToggleMessage(plugin.getCore(), fromPlayer, playerName, true, isSourceInvoker);
|
||||
plugin.getScheduler().runAsync(task);
|
||||
}
|
||||
|
||||
private void onSuccessMessage(ProxiedPlayer forPlayer) {
|
||||
if (forPlayer.getPendingConnection().isOnlineMode()) {
|
||||
//bukkit module successfully received and force logged in the user
|
||||
@@ -99,9 +115,12 @@ public class PluginMessageListener implements Listener {
|
||||
loginSession.setRegistered(true);
|
||||
|
||||
if (!loginSession.isAlreadySaved()) {
|
||||
playerProfile.setPremium(true);
|
||||
plugin.getCore().getStorage().save(playerProfile);
|
||||
loginSession.setAlreadySaved(true);
|
||||
|
||||
playerProfile.setId(loginSession.getUuid());
|
||||
playerProfile.setPremium(true);
|
||||
|
||||
plugin.getCore().getStorage().save(playerProfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,12 @@ public class AsyncPremiumCheck extends JoinManagement<ProxiedPlayer, CommandSend
|
||||
plugin.getCore().getPendingLogin().put(ip + username, new Object());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestConfirmationLogin(BungeeLoginSource source, StoredProfile profile, String username) {
|
||||
source.setOnlineMode();
|
||||
plugin.getSession().put(source.getConnection(), new BungeeLoginSession(username, profile));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startCrackedSession(BungeeLoginSource source, StoredProfile profile, String username) {
|
||||
plugin.getSession().put(source.getConnection(), new BungeeLoginSession(username, false, profile));
|
||||
|
||||
21
core/pom.xml
21
core/pom.xml
@@ -45,13 +45,6 @@
|
||||
<version>1.7.30</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 -->
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
@@ -69,17 +62,21 @@
|
||||
<dependency>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>craftapi</artifactId>
|
||||
<version>0.3</version>
|
||||
<version>0.4</version>
|
||||
</dependency>
|
||||
|
||||
<!-- APIs we can use because they are available in all platforms (Spigot, Bungee, Cauldron) -->
|
||||
<!-- APIs we can use because they are available in all platforms (Spigot, Bungee) -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<!-- 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>
|
||||
<version>17.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.2.4</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,9 @@ public class AuthStorage {
|
||||
}
|
||||
|
||||
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), "
|
||||
@@ -87,7 +90,7 @@ public class AuthStorage {
|
||||
createDataStmt = createDataStmt.replace("AUTO_INCREMENT", "AUTOINCREMENT");
|
||||
}
|
||||
|
||||
//todo: add uuid index usage
|
||||
//todo: add unique uuid index usage
|
||||
try (Connection con = dataSource.getConnection();
|
||||
Statement createStmt = con.createStatement()) {
|
||||
createStmt.executeUpdate(createDataStmt);
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
public enum ConfirmationState {
|
||||
|
||||
/**
|
||||
* Require server login where we request onlinemode authentication
|
||||
*/
|
||||
REQUIRE_RELOGIN,
|
||||
|
||||
/**
|
||||
* The command have to be invoked again to confirm that the player who joined through onlinemode knows
|
||||
* the password of the cracked account
|
||||
*/
|
||||
REQUIRE_AUTH_PLUGIN_LOGIN
|
||||
}
|
||||
@@ -6,8 +6,6 @@ import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class StoredProfile extends Profile {
|
||||
|
||||
private long rowId;
|
||||
@@ -45,7 +43,7 @@ public class StoredProfile extends Profile {
|
||||
this.rowId = generatedId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
// can be null
|
||||
public synchronized UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,9 @@ public class ChangePremiumMessage implements ChannelMessage {
|
||||
return willEnable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the player invoker = target
|
||||
*/
|
||||
public boolean isSourceInvoker() {
|
||||
return isSourceInvoker;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.github.games647.craftapi.resolver.MojangResolver;
|
||||
import com.github.games647.craftapi.resolver.http.RotatingProxySelector;
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.CommonUtil;
|
||||
import com.github.games647.fastlogin.core.ConfirmationState;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.github.games647.fastlogin.core.hooks.DefaultPasswordGenerator;
|
||||
import com.github.games647.fastlogin.core.hooks.PasswordGenerator;
|
||||
@@ -24,7 +25,6 @@ import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
@@ -47,7 +47,7 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
|
||||
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<>();
|
||||
private final ConcurrentMap<String, ConfirmationState> pendingConfirms = CommonUtil.buildCache(1, 1024);
|
||||
private final T plugin;
|
||||
|
||||
private final MojangResolver resolver = new MojangResolver();
|
||||
@@ -204,11 +204,18 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
this.passwordGenerator = passwordGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list of player names that are currently during the login process and might fail and so could be used
|
||||
* for second attempt logins
|
||||
*/
|
||||
public ConcurrentMap<String, Object> getPendingLogin() {
|
||||
return pendingLogin;
|
||||
}
|
||||
|
||||
public Collection<UUID> getPendingConfirms() {
|
||||
/**
|
||||
* @return list of player names that request onlinemode authentication but are not yet approved
|
||||
*/
|
||||
public ConcurrentMap<String, ConfirmationState> getPendingConfirms() {
|
||||
return pendingConfirms;
|
||||
}
|
||||
|
||||
@@ -240,6 +247,9 @@ 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();
|
||||
}
|
||||
|
||||
@@ -28,10 +28,16 @@ public abstract class ForceLoginManagement<P extends C, C, L extends LoginSessio
|
||||
StoredProfile playerProfile = session.getProfile();
|
||||
try {
|
||||
if (isOnlineMode()) {
|
||||
if (session.isConfirmationPending()) {
|
||||
// do not perform force actions, because this confirmation login we have to verify that it's the
|
||||
// owner of the account
|
||||
return;
|
||||
}
|
||||
|
||||
//premium player
|
||||
AuthPlugin<P> authPlugin = core.getAuthPluginHook();
|
||||
if (authPlugin == null) {
|
||||
//maybe only bungeecord plugin
|
||||
//maybe only bungeecord plugin without any auth plugins on Bungee
|
||||
onForceActionSuccess(session);
|
||||
} else {
|
||||
boolean success = true;
|
||||
|
||||
@@ -2,6 +2,7 @@ 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.ConfirmationState;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
||||
@@ -37,7 +38,14 @@ public abstract class JoinManagement<P extends C, C, S extends LoginSource> {
|
||||
if (profile.isPremium()) {
|
||||
requestPremiumLogin(source, profile, username, true);
|
||||
} else {
|
||||
startCrackedSession(source, profile, username);
|
||||
ConfirmationState confirmationState = core.getPendingConfirms().get(username);
|
||||
if (confirmationState == ConfirmationState.REQUIRE_RELOGIN) {
|
||||
core.getPendingConfirms().put(username, ConfirmationState.REQUIRE_AUTH_PLUGIN_LOGIN);
|
||||
requestPremiumLogin(source, profile, username, true);
|
||||
} else {
|
||||
// cracked player, but wants to change to premium
|
||||
startCrackedSession(source, profile, username);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (core.getPendingLogin().remove(ip + username) != null && config.get("secondAttemptCracked", false)) {
|
||||
@@ -108,5 +116,7 @@ public abstract class JoinManagement<P extends C, C, S extends LoginSource> {
|
||||
|
||||
public abstract void requestPremiumLogin(S source, StoredProfile profile, String username, boolean registered);
|
||||
|
||||
public abstract void requestConfirmationLogin(S source, StoredProfile profile, String username);
|
||||
|
||||
public abstract void startCrackedSession(S source, StoredProfile profile, String username);
|
||||
}
|
||||
|
||||
@@ -13,12 +13,19 @@ public abstract class LoginSession {
|
||||
|
||||
protected boolean registered;
|
||||
|
||||
protected boolean confirmationLogin;
|
||||
|
||||
public LoginSession(String username, boolean registered, StoredProfile profile) {
|
||||
this.username = username;
|
||||
this.registered = registered;
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
public LoginSession(String username, StoredProfile profile) {
|
||||
this(username, true, profile);
|
||||
this.confirmationLogin = true;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
@@ -54,6 +61,10 @@ public abstract class LoginSession {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public synchronized boolean isConfirmationPending() {
|
||||
return confirmationLogin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String toString() {
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
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;
|
||||
|
||||
@@ -15,6 +18,8 @@ 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);
|
||||
@@ -22,6 +27,11 @@ public interface PlatformPlugin<C> {
|
||||
}
|
||||
|
||||
default ThreadFactory getThreadFactory() {
|
||||
return null;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,11 +115,11 @@ nameChangeCheck: false
|
||||
# ChangeSkin, SkinRestorer, ...
|
||||
forwardSkin: true
|
||||
|
||||
# Displays a warning message that this message SHOULD only be invoked by
|
||||
# users who actually are the owner of this account. So not by cracked players
|
||||
#
|
||||
# If they still want to invoke the command, they have to invoke /premium again
|
||||
premium-warning: true
|
||||
# Players have to rejoin to verify that they can join through onlinemode authentication.
|
||||
# After that they have to confirm again using the /premium command that they are also the owner of the auth plugin
|
||||
# account.
|
||||
# If the onlinemode authentication fails or the player waits to long, it will fallback to offlinemode authentication.
|
||||
premium-confirm: true
|
||||
|
||||
# If you have autoRegister or nameChangeCheck enabled, you could be rate-limited by Mojang.
|
||||
# The requests of the both options will be only made by FastLogin if the username is unknown to the server
|
||||
|
||||
@@ -89,9 +89,9 @@ invalid-requst: '&4Invalid request'
|
||||
# Message if the Bukkit isn't fully started to inject the packets
|
||||
not-started: '&cServer is not fully started yet. Please retry'
|
||||
|
||||
# Warning message if a user invoked /premium command
|
||||
premium-warning: '&c&lWARNING: &6This command should &lonly&6 be invoked if you are the owner of this paid Minecraft account
|
||||
Type &a/premium&6 again to confirm'
|
||||
# Premium confirmation message if the player tries to activate the command
|
||||
premium-confirm: '&6Please relogin and type the command again to apply the changes.
|
||||
If the request fails or you wait to long, it will fallback to offlinemode.'
|
||||
|
||||
# ========= Bungee/Waterfall only ================================
|
||||
|
||||
|
||||
Reference in New Issue
Block a user