From e639e29dee0b4930ab0a93319a3ea94d678cb571 Mon Sep 17 00:00:00 2001 From: juanmuscaria Date: Mon, 13 Sep 2021 21:24:46 -0300 Subject: [PATCH] Partial velocity support. --- pom.xml | 1 + velocity/pom.xml | 136 +++++++++++++++ .../fastlogin/velocity/FastLoginVelocity.java | 125 ++++++++++++++ .../velocity/VelocityLoginSession.java | 40 +++++ .../velocity/VelocityLoginSource.java | 50 ++++++ .../VelocityFastLoginAutoLoginEvent.java | 76 +++++++++ .../event/VelocityFastLoginPreLoginEvent.java | 58 +++++++ .../VelocityFastLoginPremiumToggleEvent.java | 50 ++++++ .../velocity/listener/ConnectListener.java | 156 ++++++++++++++++++ .../listener/PluginMessageListener.java | 132 +++++++++++++++ .../velocity/task/AsyncPremiumCheck.java | 100 +++++++++++ .../velocity/task/AsyncToggleMessage.java | 110 ++++++++++++ .../velocity/task/ForceLoginTask.java | 141 ++++++++++++++++ .../src/main/resources/velocity-plugin.json | 9 + 14 files changed, 1184 insertions(+) create mode 100644 velocity/pom.xml create mode 100644 velocity/src/main/java/com/github/games647/fastlogin/velocity/FastLoginVelocity.java create mode 100644 velocity/src/main/java/com/github/games647/fastlogin/velocity/VelocityLoginSession.java create mode 100644 velocity/src/main/java/com/github/games647/fastlogin/velocity/VelocityLoginSource.java create mode 100644 velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginAutoLoginEvent.java create mode 100644 velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginPreLoginEvent.java create mode 100644 velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginPremiumToggleEvent.java create mode 100644 velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/ConnectListener.java create mode 100644 velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/PluginMessageListener.java create mode 100644 velocity/src/main/java/com/github/games647/fastlogin/velocity/task/AsyncPremiumCheck.java create mode 100644 velocity/src/main/java/com/github/games647/fastlogin/velocity/task/AsyncToggleMessage.java create mode 100644 velocity/src/main/java/com/github/games647/fastlogin/velocity/task/ForceLoginTask.java create mode 100644 velocity/src/main/resources/velocity-plugin.json diff --git a/pom.xml b/pom.xml index 6031c250..f3971014 100644 --- a/pom.xml +++ b/pom.xml @@ -57,6 +57,7 @@ core bukkit bungee + velocity diff --git a/velocity/pom.xml b/velocity/pom.xml new file mode 100644 index 00000000..2076c23d --- /dev/null +++ b/velocity/pom.xml @@ -0,0 +1,136 @@ + + + 4.0.0 + + + com.github.games647 + fastlogin + 1.11-SNAPSHOT + ../pom.xml + + + + fastlogin.velocity + jar + + + FastLoginVelocity + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + false + false + + + com.zaxxer.hikari + fastlogin.hikari + + + org.slf4j + fastlogin.slf4j + + + net.md_5.bungee.config + fastlogin.config + + + + + + package + + shade + + + + + + + + + + + codemc-repo + https://repo.codemc.io/repository/maven-public/ + + + + opencollab-snapshot + https://repo.opencollab.dev/maven-snapshots/ + + + + spigotplugins-repo + https://maven.gamestrike.de/mvn/ + + + + velocity + https://nexus.velocitypowered.com/repository/maven-public/ + + + + jitpack.io + https://jitpack.io + + false + + + + + + + + ${project.groupId} + fastlogin.core + ${project.version} + + + + + com.velocitypowered + velocity-api + 3.0.1 + provided + + + + org.xerial + sqlite-jdbc + 3.32.3 + + + + diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/FastLoginVelocity.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/FastLoginVelocity.java new file mode 100644 index 00000000..501db459 --- /dev/null +++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/FastLoginVelocity.java @@ -0,0 +1,125 @@ +package com.github.games647.fastlogin.velocity; + +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; +import com.github.games647.fastlogin.core.message.NamespaceKey; +import com.github.games647.fastlogin.core.message.SuccessMessage; +import com.github.games647.fastlogin.core.shared.FastLoginCore; +import com.github.games647.fastlogin.core.shared.PlatformPlugin; +import com.github.games647.fastlogin.velocity.listener.ConnectListener; +import com.github.games647.fastlogin.velocity.listener.PluginMessageListener; +import com.google.common.collect.MapMaker; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import com.google.inject.Inject; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.player.PlayerChatEvent; +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.InboundConnection; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.UUID; +import java.util.concurrent.ConcurrentMap; + +import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.slf4j.Logger; + +@Plugin(id = "fastlogin") +public class FastLoginVelocity implements PlatformPlugin { + private final ProxyServer server; + private final Path dataDirectory; + private final Logger logger; + private FastLoginCore core; + private final ConcurrentMap session = new MapMaker().weakKeys().makeMap(); + private AsyncScheduler scheduler; + + @Inject + public FastLoginVelocity(ProxyServer server, java.util.logging.Logger logger, @DataDirectory Path dataDirectory) { + this.server = server; + this.logger = CommonUtil.createLoggerFromJDK(logger); + this.dataDirectory = dataDirectory; + logger.info("FastLogin velocity."); + } + + @Subscribe + public void onProxyInitialization(ProxyInitializeEvent event) { + scheduler = new AsyncScheduler(logger, getThreadFactory()); + + core = new FastLoginCore<>(this); + core.load(); + if (!core.setupDatabase()) { + return; + } + logger.info("Velocity uuid for allowed proxies:" + UUID.nameUUIDFromBytes("velocity".getBytes(StandardCharsets.UTF_8))); + server.getChannelRegistrar().register(MinecraftChannelIdentifier.create(getName(), ChangePremiumMessage.CHANGE_CHANNEL)); + server.getChannelRegistrar().register(MinecraftChannelIdentifier.create(getName(), SuccessMessage.SUCCESS_CHANNEL)); + server.getEventManager().register(this, new ConnectListener(this, core.getRateLimiter())); + server.getEventManager().register(this, new PluginMessageListener(this)); + } + + @Override + public String getName() { + //FIXME: some dynamic way to get it? + return "fastlogin"; + } + + @Override + public Path getPluginFolder() { + return dataDirectory; + } + + @Override + public Logger getLog() { + return logger; + } + + @Override + public void sendMessage(CommandSource receiver, String message) { + receiver.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(message)); + + } + + @Override + public AsyncScheduler getScheduler() { + return scheduler; + } + + @Override + public boolean isPluginInstalled(String name) { + return server.getPluginManager().isLoaded(name); + } + + public FastLoginCore getCore() { + return core; + } + + public ConcurrentMap getSession() { + return session; + } + + public ProxyServer getProxy() { + return server; + } + + public void sendPluginMessage(RegisteredServer server, ChannelMessage message) { + if (server != null) { + ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput(); + message.writeTo(dataOutput); + + MinecraftChannelIdentifier channel = MinecraftChannelIdentifier.create(getName(), message.getChannelName()); + server.sendPluginMessage(channel, dataOutput.toByteArray()); + } + } +} diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/VelocityLoginSession.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/VelocityLoginSession.java new file mode 100644 index 00000000..9d865a48 --- /dev/null +++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/VelocityLoginSession.java @@ -0,0 +1,40 @@ +package com.github.games647.fastlogin.velocity; + +import com.github.games647.fastlogin.core.StoredProfile; +import com.github.games647.fastlogin.core.shared.LoginSession; + +public class VelocityLoginSession extends LoginSession { + private boolean alreadySaved; + private boolean alreadyLogged; + public VelocityLoginSession(String requestUsername, boolean registered, StoredProfile profile) { + super(requestUsername, registered, profile); + } + public synchronized void setRegistered(boolean registered) { + this.registered = registered; + } + + public synchronized boolean isAlreadySaved() { + return alreadySaved; + } + + public synchronized void setAlreadySaved(boolean alreadySaved) { + this.alreadySaved = alreadySaved; + } + + public synchronized boolean isAlreadyLogged() { + return alreadyLogged; + } + + public synchronized void setAlreadyLogged(boolean alreadyLogged) { + this.alreadyLogged = alreadyLogged; + } + + @Override + public synchronized String toString() { + return this.getClass().getSimpleName() + '{' + + "alreadySaved=" + alreadySaved + + ", alreadyLogged=" + alreadyLogged + + ", registered=" + registered + + "} " + super.toString(); + } +} diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/VelocityLoginSource.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/VelocityLoginSource.java new file mode 100644 index 00000000..abd3ef78 --- /dev/null +++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/VelocityLoginSource.java @@ -0,0 +1,50 @@ +package com.github.games647.fastlogin.velocity; + +import com.github.games647.fastlogin.core.shared.LoginSource; +import com.velocitypowered.api.event.Continuation; +import com.velocitypowered.api.event.connection.PreLoginEvent; +import com.velocitypowered.api.proxy.InboundConnection; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentBuilder; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +import java.net.InetSocketAddress; + +public class VelocityLoginSource implements LoginSource { + + private InboundConnection connection; + private PreLoginEvent preLoginEvent; + + public VelocityLoginSource(InboundConnection connection, PreLoginEvent preLoginEvent) { + this.connection = connection; + this.preLoginEvent = preLoginEvent; + } + + @Override + public void enableOnlinemode() { + preLoginEvent.setResult(PreLoginEvent.PreLoginComponentResult.forceOnlineMode()); + } + + @Override + public void kick(String message) { + + + if (message == null) { + preLoginEvent.setResult(PreLoginEvent.PreLoginComponentResult.denied( + Component.text("Kicked").color(NamedTextColor.WHITE))); + } else { + preLoginEvent.setResult(PreLoginEvent.PreLoginComponentResult.denied( + LegacyComponentSerializer.legacyAmpersand().deserialize(message))); + } + } + + @Override + public InetSocketAddress getAddress() { + return connection.getRemoteAddress(); + } + + public InboundConnection getConnection() { + return connection; + } +} diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginAutoLoginEvent.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginAutoLoginEvent.java new file mode 100644 index 00000000..09b72603 --- /dev/null +++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginAutoLoginEvent.java @@ -0,0 +1,76 @@ +/* + * SPDX-License-Identifier: MIT + * + * The MIT License (MIT) + * + * Copyright (c) 2015-2021 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.games647.fastlogin.velocity.event; + +import com.github.games647.fastlogin.core.StoredProfile; +import com.github.games647.fastlogin.core.shared.LoginSession; +import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent; +import com.velocitypowered.api.event.ResultedEvent; + +import java.util.Objects; + +public class VelocityFastLoginAutoLoginEvent implements FastLoginAutoLoginEvent, ResultedEvent { + + private final LoginSession session; + private final StoredProfile profile; + private boolean cancelled; + + public VelocityFastLoginAutoLoginEvent(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; + } + + + @Override + public GenericResult getResult() { + return cancelled ? GenericResult.denied(): GenericResult.allowed(); + } + + @Override + public void setResult(GenericResult result) { + cancelled = Objects.requireNonNull(result) != GenericResult.allowed(); + } +} diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginPreLoginEvent.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginPreLoginEvent.java new file mode 100644 index 00000000..ce58ee52 --- /dev/null +++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginPreLoginEvent.java @@ -0,0 +1,58 @@ +/* + * SPDX-License-Identifier: MIT + * + * The MIT License (MIT) + * + * Copyright (c) 2015-2021 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.games647.fastlogin.velocity.event; + +import com.github.games647.fastlogin.core.StoredProfile; +import com.github.games647.fastlogin.core.shared.LoginSource; +import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent; + +public class VelocityFastLoginPreLoginEvent implements FastLoginPreLoginEvent { + + private final String username; + private final LoginSource source; + private final StoredProfile profile; + + public VelocityFastLoginPreLoginEvent(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; + } +} diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginPremiumToggleEvent.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginPremiumToggleEvent.java new file mode 100644 index 00000000..3dc83b1a --- /dev/null +++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/event/VelocityFastLoginPremiumToggleEvent.java @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: MIT + * + * The MIT License (MIT) + * + * Copyright (c) 2015-2021 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.games647.fastlogin.velocity.event; + +import com.github.games647.fastlogin.core.StoredProfile; +import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent; + +public class VelocityFastLoginPremiumToggleEvent implements FastLoginPremiumToggleEvent { + + private final StoredProfile profile; + private final PremiumToggleReason reason; + + public VelocityFastLoginPremiumToggleEvent(StoredProfile profile, PremiumToggleReason reason) { + this.profile = profile; + this.reason = reason; + } + + @Override + public StoredProfile getProfile() { + return profile; + } + + @Override + public PremiumToggleReason getReason() { + return reason; + } +} diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/ConnectListener.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/ConnectListener.java new file mode 100644 index 00000000..b7e00492 --- /dev/null +++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/ConnectListener.java @@ -0,0 +1,156 @@ +/* + * SPDX-License-Identifier: MIT + * + * The MIT License (MIT) + * + * Copyright (c) 2015-2021 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.games647.fastlogin.velocity.listener; + +import com.github.games647.craftapi.UUIDAdapter; +import com.github.games647.fastlogin.core.RateLimiter; +import com.github.games647.fastlogin.core.StoredProfile; +import com.github.games647.fastlogin.core.shared.LoginSession; +import com.github.games647.fastlogin.velocity.FastLoginVelocity; +import com.github.games647.fastlogin.velocity.VelocityLoginSession; +import com.github.games647.fastlogin.velocity.task.AsyncPremiumCheck; +import com.github.games647.fastlogin.velocity.task.ForceLoginTask; +import com.google.common.base.Throwables; +import com.velocitypowered.api.event.Continuation; +import com.velocitypowered.api.event.PostOrder; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.DisconnectEvent; +import com.velocitypowered.api.event.connection.LoginEvent; + +import com.velocitypowered.api.event.connection.PreLoginEvent; +import com.velocitypowered.api.event.player.ServerConnectedEvent; +import com.velocitypowered.api.proxy.InboundConnection; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.server.RegisteredServer; + +import java.util.UUID; + +public class ConnectListener { + + private final FastLoginVelocity plugin; + private final RateLimiter rateLimiter; + + public ConnectListener(FastLoginVelocity plugin, RateLimiter rateLimiter) { + this.plugin = plugin; + this.rateLimiter = rateLimiter; + } + + @Subscribe() + public void onPreLogin(PreLoginEvent preLoginEvent, Continuation continuation) { + if (!preLoginEvent.getResult().isAllowed()) { + return; + } + InboundConnection connection = preLoginEvent.getConnection(); + if (!rateLimiter.tryAcquire()) { + plugin.getLog().warn("Simple Anti-Bot join limit - Ignoring {}", connection); + return; + } + + String username = preLoginEvent.getUsername(); + plugin.getLog().info("Incoming login request for {} from {}", username, connection.getRemoteAddress()); + + + Runnable asyncPremiumCheck = new AsyncPremiumCheck(plugin, connection, username, continuation, preLoginEvent); + plugin.getScheduler().runAsync(asyncPremiumCheck); + } + + @Subscribe(order = PostOrder.LATE) + public void onLogin(LoginEvent loginEvent) { + //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 + Player connection = loginEvent.getPlayer(); + if (connection.isOnlineMode()) { + LoginSession session = plugin.getSession().get(connection.getRemoteAddress()); + + UUID verifiedUUID = connection.getUniqueId(); + String verifiedUsername = connection.getUsername(); + session.setUuid(verifiedUUID); + session.setVerifiedUsername(verifiedUsername); + + StoredProfile playerProfile = session.getProfile(); + playerProfile.setId(verifiedUUID); + + // bungeecord will do this automatically so override it on disabled option +// if (uniqueIdSetter != null) { +// InitialHandler initialHandler = (InitialHandler) connection; +// +// if (!plugin.getCore().getConfig().get("premiumUuid", true)) { +// setOfflineId(initialHandler, verifiedUsername); +// } +// +// if (!plugin.getCore().getConfig().get("forwardSkin", true)) { +// // this is null on offline mode +// LoginResult loginProfile = initialHandler.getLoginProfile(); +// 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); +// } +// } + + @Subscribe + public void onServerConnected(ServerConnectedEvent serverConnectedEvent) { + Player player = serverConnectedEvent.getPlayer(); + RegisteredServer server = serverConnectedEvent.getServer(); + + VelocityLoginSession session = plugin.getSession().get(player.getRemoteAddress()); + 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); + } + + @Subscribe + public void onDisconnect(DisconnectEvent disconnectEvent) { + Player player = disconnectEvent.getPlayer(); + assert plugin.getSession().remove(player.getRemoteAddress()) != null; + plugin.getCore().getPendingConfirms().remove(player.getUniqueId()); + } +} diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/PluginMessageListener.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/PluginMessageListener.java new file mode 100644 index 00000000..43f09e5d --- /dev/null +++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/PluginMessageListener.java @@ -0,0 +1,132 @@ +/* + * SPDX-License-Identifier: MIT + * + * The MIT License (MIT) + * + * Copyright (c) 2015-2021 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.games647.fastlogin.velocity.listener; + +import com.github.games647.fastlogin.core.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.core.shared.FastLoginCore; +import com.github.games647.fastlogin.velocity.FastLoginVelocity; +import com.github.games647.fastlogin.velocity.VelocityLoginSession; +import com.github.games647.fastlogin.velocity.task.AsyncToggleMessage; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteStreams; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.PluginMessageEvent; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +import java.util.Arrays; + +public class PluginMessageListener { + + private final FastLoginVelocity plugin; + + private final String successChannel; + private final String changeChannel; + + public PluginMessageListener(FastLoginVelocity plugin) { + this.plugin = plugin; + + this.successChannel = MinecraftChannelIdentifier.create(plugin.getName(), SuccessMessage.SUCCESS_CHANNEL).getId(); + this.changeChannel = MinecraftChannelIdentifier.create(plugin.getName(), ChangePremiumMessage.CHANGE_CHANNEL).getId(); + } + + @Subscribe + public void onPluginMessage(PluginMessageEvent pluginMessageEvent) { + String channel = pluginMessageEvent.getIdentifier().getId(); + if (!pluginMessageEvent.getResult().isAllowed() || !channel.startsWith(plugin.getName().toLowerCase())) { + return; + } + + //the client shouldn't be able to read the messages in order to know something about server internal states + //moreover the client shouldn't be able fake a running premium check by sending the result message + pluginMessageEvent.setResult(PluginMessageEvent.ForwardResult.handled()); + + if (!(pluginMessageEvent.getSource() instanceof ServerConnection)) { + //check if the message is sent from the server + return; + } + + //so that we can safely process this in the background + byte[] data = Arrays.copyOf(pluginMessageEvent.getData(), pluginMessageEvent.getData().length); + Player forPlayer = (Player) pluginMessageEvent.getTarget(); + + plugin.getScheduler().runAsync(() -> readMessage(forPlayer, channel, data)); + } + + private void readMessage(Player forPlayer, String channel, byte[] data) { + FastLoginCore core = plugin.getCore(); + + ByteArrayDataInput dataInput = ByteStreams.newDataInput(data); + if (successChannel.equals(channel)) { + onSuccessMessage(forPlayer); + } 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.getUsername()) && plugin.getCore().getConfig().get("premium-warning", true) + && !core.getPendingConfirms().contains(forPlayer.getUniqueId())) { + String message = core.getMessage("premium-warning"); + forPlayer.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(message)); + core.getPendingConfirms().add(forPlayer.getUniqueId()); + return; + } + + core.getPendingConfirms().remove(forPlayer.getUniqueId()); + Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, true, isSourceInvoker); + plugin.getScheduler().runAsync(task); + } else { + Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, false, isSourceInvoker); + plugin.getScheduler().runAsync(task); + } + } + } + + private void onSuccessMessage(Player forPlayer) { + + if (forPlayer.isOnlineMode()){ + //bukkit module successfully received and force logged in the user + //update only on success to prevent corrupt data + VelocityLoginSession loginSession = plugin.getSession().get(forPlayer.getRemoteAddress()); + StoredProfile playerProfile = loginSession.getProfile(); + loginSession.setRegistered(true); + if (!loginSession.isAlreadySaved()) { + playerProfile.setPremium(true); + plugin.getCore().getStorage().save(playerProfile); + loginSession.setAlreadySaved(true); + } + } + } +} diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/AsyncPremiumCheck.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/AsyncPremiumCheck.java new file mode 100644 index 00000000..30b6dd17 --- /dev/null +++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/AsyncPremiumCheck.java @@ -0,0 +1,100 @@ +/* + * SPDX-License-Identifier: MIT + * + * The MIT License (MIT) + * + * Copyright (c) 2015-2021 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.games647.fastlogin.velocity.task; + +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 com.github.games647.fastlogin.velocity.FastLoginVelocity; +import com.github.games647.fastlogin.velocity.VelocityLoginSession; +import com.github.games647.fastlogin.velocity.VelocityLoginSource; +import com.github.games647.fastlogin.velocity.event.VelocityFastLoginPreLoginEvent; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.event.Continuation; +import com.velocitypowered.api.event.connection.PreLoginEvent; +import com.velocitypowered.api.proxy.InboundConnection; +import com.velocitypowered.api.proxy.Player; + +import java.util.concurrent.ExecutionException; + +public class AsyncPremiumCheck extends JoinManagement + implements Runnable { + + private final FastLoginVelocity plugin; + private final String username; + private Continuation continuation; + private PreLoginEvent preLoginEvent; + private final InboundConnection connection; + + public AsyncPremiumCheck(FastLoginVelocity plugin, InboundConnection connection, String username, Continuation continuation, PreLoginEvent preLoginEvent) { + super(plugin.getCore(), plugin.getCore().getAuthPluginHook()); + this.plugin = plugin; + this.connection = connection; + this.username = username; + this.continuation = continuation; + this.preLoginEvent = preLoginEvent; + } + + @Override + public void run() { + plugin.getSession().remove(connection.getRemoteAddress()); + try { + super.onLogin(username, new VelocityLoginSource(connection, preLoginEvent)); + } finally { + continuation.resume(); + } + } + + @Override + public FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, VelocityLoginSource source, StoredProfile profile) { + //FIXME: Am I doing it right? + VelocityFastLoginPreLoginEvent event = new VelocityFastLoginPreLoginEvent(username, source, profile); + try { + return plugin.getProxy().getEventManager().fire(event).get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); // Restore the interrupt flag + return event; + } catch (ExecutionException e) { + e.printStackTrace(); + return event; + } + } + + @Override + public void requestPremiumLogin(VelocityLoginSource source, StoredProfile profile, + String username, boolean registered) { + source.enableOnlinemode(); + plugin.getSession().put(source.getConnection().getRemoteAddress(), new VelocityLoginSession(username, registered, profile)); + + String ip = source.getAddress().getAddress().getHostAddress(); + plugin.getCore().getPendingLogin().put(ip + username, new Object()); + } + + @Override + public void startCrackedSession(VelocityLoginSource source, StoredProfile profile, String username) { + plugin.getSession().put(source.getConnection().getRemoteAddress(), new VelocityLoginSession(username, false, profile)); + } +} diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/AsyncToggleMessage.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/AsyncToggleMessage.java new file mode 100644 index 00000000..d1b24cf5 --- /dev/null +++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/AsyncToggleMessage.java @@ -0,0 +1,110 @@ +/* + * SPDX-License-Identifier: MIT + * + * The MIT License (MIT) + * + * Copyright (c) 2015-2021 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.games647.fastlogin.velocity.task; + +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 com.github.games647.fastlogin.velocity.FastLoginVelocity; +import com.github.games647.fastlogin.velocity.event.VelocityFastLoginPremiumToggleEvent; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.Player; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +public class AsyncToggleMessage implements Runnable { + + private final FastLoginCore core; + private final CommandSource sender; + private final String senderName; + private final String targetPlayer; + private final boolean toPremium; + private final boolean isPlayerSender; + + public AsyncToggleMessage(FastLoginCore core, + CommandSource sender, String playerName, boolean toPremium, boolean playerSender) { + this.core = core; + this.sender = sender; + this.targetPlayer = playerName; + this.toPremium = toPremium; + this.isPlayerSender = playerSender; + if (sender instanceof Player) + senderName = ((Player) sender).getUsername(); + else + senderName = ""; + } + + @Override + public void run() { + if (toPremium) { + activatePremium(); + } else { + turnOffPremium(); + } + } + + private void turnOffPremium() { + StoredProfile playerProfile = core.getStorage().loadProfile(targetPlayer); + //existing player is already cracked + if (playerProfile.isSaved() && !playerProfile.isPremium()) { + sendMessage("not-premium"); + return; + } + + playerProfile.setPremium(false); + playerProfile.setId(null); + core.getStorage().save(playerProfile); + PremiumToggleReason reason = (!isPlayerSender || !senderName.equalsIgnoreCase(playerProfile.getName())) ? + PremiumToggleReason.COMMAND_OTHER : PremiumToggleReason.COMMAND_SELF; + core.getPlugin().getProxy().getEventManager().fire( + new VelocityFastLoginPremiumToggleEvent(playerProfile, reason)); + sendMessage("remove-premium"); + } + + private void activatePremium() { + StoredProfile playerProfile = core.getStorage().loadProfile(targetPlayer); + if (playerProfile.isPremium()) { + sendMessage("already-exists"); + return; + } + + playerProfile.setPremium(true); + core.getStorage().save(playerProfile); + PremiumToggleReason reason = (!isPlayerSender || !senderName.equalsIgnoreCase(playerProfile.getName())) ? + PremiumToggleReason.COMMAND_OTHER : PremiumToggleReason.COMMAND_SELF; + core.getPlugin().getProxy().getEventManager().fire( + new VelocityFastLoginPremiumToggleEvent(playerProfile, reason)); + sendMessage("add-premium"); + } + + private void sendMessage(String localeId) { + String message = core.getMessage(localeId); + if (isPlayerSender) { + sender.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(message)); + } else { + core.getPlugin().getProxy().getConsoleCommandSource().sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(message)); + } + } +} diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/ForceLoginTask.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/ForceLoginTask.java new file mode 100644 index 00000000..50ad92e1 --- /dev/null +++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/ForceLoginTask.java @@ -0,0 +1,141 @@ +/* + * SPDX-License-Identifier: MIT + * + * The MIT License (MIT) + * + * Copyright (c) 2015-2021 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.games647.fastlogin.velocity.task; + +import com.github.games647.fastlogin.core.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.shared.FastLoginCore; +import com.github.games647.fastlogin.core.shared.ForceLoginManagement; +import com.github.games647.fastlogin.core.shared.LoginSession; +import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent; +import com.github.games647.fastlogin.velocity.FastLoginVelocity; +import com.github.games647.fastlogin.velocity.VelocityLoginSession; +import com.github.games647.fastlogin.velocity.event.VelocityFastLoginAutoLoginEvent; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.server.RegisteredServer; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +public class ForceLoginTask + extends ForceLoginManagement { + + private final RegisteredServer server; + + //treat player as if they had a premium account, even when they don't + //used for Floodgate auto login/register + private final boolean forcedOnlineMode; + + public ForceLoginTask(FastLoginCore core, + Player player, RegisteredServer server, VelocityLoginSession session, boolean forcedOnlineMode) { + super(core, player, session); + + this.server = server; + this.forcedOnlineMode = forcedOnlineMode; + } + + public ForceLoginTask(FastLoginCore core, Player player, + RegisteredServer server, VelocityLoginSession session) { + this(core, player, server, session, false); + } + + @Override + public void run() { + if (session == null) { + return; + } + + super.run(); + if (!isOnlineMode()) { + session.setAlreadySaved(true); + } + } + + @Override + public boolean forceLogin(Player player) { + if (session.isAlreadyLogged()) { + return true; + } + + session.setAlreadyLogged(true); + return super.forceLogin(player); + } + + @Override + public FastLoginAutoLoginEvent callFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile) { + //FIXME: Am I doing it right? + VelocityFastLoginAutoLoginEvent event = new VelocityFastLoginAutoLoginEvent(session, profile); + try { + return core.getPlugin().getProxy().getEventManager().fire(event).get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); // Set the interrupt flag again + return event; + } catch (ExecutionException e) { + e.printStackTrace(); + return event; + } + } + + @Override + public boolean forceRegister(Player player) { + return session.isAlreadyLogged() || super.forceRegister(player); + } + + @Override + public void onForceActionSuccess(LoginSession session) { + //sub channel name + Type type = Type.LOGIN; + if (session.needsRegistration()) { + type = Type.REGISTER; + } + //FIXME: Velocity does not have an alternative for this! + //UUID proxyId = UUID.fromString(ProxyServer.getInstance().getConfig().getUuid()); + UUID proxyId = UUID.nameUUIDFromBytes("velocity".getBytes(StandardCharsets.UTF_8)); + ChannelMessage loginMessage = new LoginActionMessage(type, player.getUsername(), proxyId); + + core.getPlugin().sendPluginMessage(server, loginMessage); + } + + @Override + public String getName(Player player) { + return player.getUsername(); + } + + @Override + public boolean isOnline(Player player) { + //FIXME: is this right? + return core.getPlugin().getProxy().getPlayer(player.getUsername()).isPresent(); + } + + @Override + public boolean isOnlineMode() { + return forcedOnlineMode || player.isOnlineMode(); + } +} diff --git a/velocity/src/main/resources/velocity-plugin.json b/velocity/src/main/resources/velocity-plugin.json new file mode 100644 index 00000000..fc1dce76 --- /dev/null +++ b/velocity/src/main/resources/velocity-plugin.json @@ -0,0 +1,9 @@ +{ + "id":"fastlogin", + "name":"${project.parent.name}", + "version":"${project.version}-${git.commit.id.abbrev}", + "authors":["games647", "https://github.com/games647/FastLogin/graphs/contributors"], + "dependencies":[], + "main":"${project.groupId}.${project.artifactId}.${project.name}" + +} \ No newline at end of file