Compare commits

..

2 Commits

Author SHA1 Message Date
games647
44abe78213 Re-enable papermc release repo 2023-05-22 15:11:39 +02:00
games647
6f69f73bad Include updated yaml for older Spigot platforms
Fixes #1031
2023-05-22 15:08:12 +02:00
39 changed files with 168 additions and 808 deletions

View File

@@ -36,7 +36,7 @@ body:
attributes:
label: Server log
description: The error, stacktrace or link the complete log. You can use the links above for long versions.
placeholder: https://www.toptal.com/developers/hastebin / https://gist.github.com/
placeholder: https://hastebin.com/ / https://gist.github.com/
- type: input
attributes:
label: Plugin version
@@ -59,11 +59,9 @@ body:
label: Relevance
description: Check list for previous tickets
options:
- label: |
I tried the [latest build](https://ci.codemc.io/job/Games647/job/FastLogin/)
(build refers to development builds not necessary a release version; i.e. v1.10 is out of date)
- label: I tried the latest build (build refers to development builds not necessary a release version)
required: true
- label: |
I checked for existing tickets -
If there are, please vote them with a thumbs reaction and not create new ones
If there are, please vote them with a thumps reaction and not create new ones
required: true

View File

@@ -0,0 +1,30 @@
---
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.)
[//]: # (Before reporting make sure you're running the **latest build** of the plugin and checked for existing issues!)
[//]: # (This ticket is about suggestions for a feature or particular enhancement)
### 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,43 +0,0 @@
name: 💡 Enhancement request
description: New feature or change request
labels: [ enhancement ]
body:
- type: markdown
attributes:
value: |
This ticket is about suggestions for a feature or particular enhancement.
Feedback about this form is appreciated.
- type: textarea
attributes:
label: Is your feature request related to a problem? Please describe.
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
validations:
required: true
- type: textarea
attributes:
label: Describe the solution you'd like
description: A clear and concise description of what you want to happen.
- type: textarea
attributes:
label: Describe alternatives you've considered
description: A clear and concise description of any alternative solutions or features you've considered.
- type: dropdown
attributes:
label: Platform
description: Server software - choose your proxy software if you have multiple servers
options:
- Spigot
- BungeeCord
- Velocity
- All / Shared
validations:
required: true
- type: checkboxes
attributes:
label: Relevance
description: Check list for previous tickets
options:
- label: |
I checked for existing tickets -
If there are, please vote them with a thumbs reaction and not create new ones
required: true

View File

@@ -14,22 +14,3 @@ updates:
directory: "/"
schedule:
interval: weekly
groups:
production-dependencies:
dependency-type: "production"
development-dependencies:
dependency-type: "development"
exclude-patterns:
# Create single PR for these
# Plugin require special evaluation about their compatibility
- "com.lenis0012.bukkit:loginsecurity"
- "com.comphenix.protocol:ProtocolLib"
ignore:
# HikariCP dropped Java 8 support with 5.0
- dependency-name: "com.zaxxer:HikariCP"
update-types: ["version-update:semver-major"]
# SnakeYAML has breaking changes with 2.0
- dependency-name: "org.yaml:snakeyaml"
update-types: ["version-update:semver-major"]

View File

@@ -4,11 +4,11 @@
name: "CodeQL"
on:
workflow_run:
workflows: ["Maven Build"]
branches: [main]
types:
- completed
# Scan only for push on the primary branch for now
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
# job i
@@ -20,10 +20,6 @@ jobs:
# Environment
runs-on: ubuntu-latest
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
if: ${{ github.event.workflow_run.conclusion == 'success' }}
permissions:
actions: read
contents: read
@@ -37,29 +33,28 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
uses: actions/checkout@v3
# Setup Java
- name: Set up JDK
uses: actions/setup-java@v4
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version-file: '.java-version'
java-version: 19
cache: 'maven'
# Manually start the autobuild process, because autobuild always selects Java 8 as build toolchain, but
# we are doing cross-crompiling from a newer Java version
- name: Build with Maven
# Extracted from autobuild
run: mvn package -f "pom.xml" --batch-mode -V -e -Dfindbugs.skip -Dcheckstyle.skip -Dpmd.skip=true -Dspotbugs.skip -Denforcer.skip -Dmaven.javadoc.skip -DskipTests -Dmaven.test.skip.exec -Dlicense.skip=true -Drat.skip=true -Dspotless.check.skip=true -t /home/runner/.m2/toolchains.xml
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# Auto build attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

View File

@@ -21,19 +21,19 @@ jobs:
# Environment image - always use the newest OS
runs-on: ubuntu-latest
permissions:
contents: write
contents: read
# Run steps
steps:
# Pull changes
- uses: actions/checkout@v4
- uses: actions/checkout@v3
# Setup Java
- name: Set up JDK
uses: actions/setup-java@v4
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version-file: '.java-version'
java-version: 19
cache: 'maven'
# Build and test (included in package)
@@ -41,7 +41,3 @@ jobs:
# Run non-interactive, package (with compile+test),
# ignore snapshot updates, because they are likely to have breaking changes, enforce checksums
run: mvn test --batch-mode --threads 2.0C --no-snapshot-updates --strict-checksums --file pom.xml
- name: Update dependency graph
if: ${{ github.event_name == 'push' }}
uses: advanced-security/maven-dependency-submission-action@v4.0.0

View File

@@ -1,2 +0,0 @@
# Latest GitHub Action java release that is installed on the runners
21

View File

@@ -50,7 +50,7 @@
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<version>3.4.1</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<shadedArtifactAttached>false</shadedArtifactAttached>
@@ -207,7 +207,7 @@
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>5.1.0</version>
<version>5.0.0</version>
<scope>provided</scope>
<exclusions>
<exclusion>
@@ -278,7 +278,7 @@
<dependency>
<groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId>
<version>2.11.5</version>
<version>2.11.3</version>
<scope>provided</scope>
<optional>true</optional>
<exclusions>
@@ -307,7 +307,7 @@
<dependency>
<groupId>com.lenis0012.bukkit</groupId>
<artifactId>loginsecurity</artifactId>
<version>3.3.0</version>
<version>3.1.1</version>
<scope>provided</scope>
<optional>true</optional>
<exclusions>
@@ -378,7 +378,7 @@
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.77</version>
<version>1.72</version>
<scope>test</scope>
</dependency>

View File

@@ -228,22 +228,6 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
/**
* Fetches the premium status of an online player.
* {@snippet :
* // Bukkit's players object after successful authentication i.e. PlayerJoinEvent
* // except for proxies like BungeeCord and Velocity where the details are sent delayed (1-2 seconds)
* Player player;
* PremiumStatus status = JavaPlugin.getPlugin(FastLoginBukkit.class).getStatus(player.getUniqueId());
* switch (status) {
* case CRACKED:
* // player is offline
* break;
* case PREMIUM:
* // account is premium and player passed the verification
* break;
* case UNKNOWN:
* // no record about this player
* }
* }
*
* @param onlinePlayer player that is currently online player (play state)
* @return the online status or unknown if an error happened, the player isn't online or BungeeCord doesn't send

View File

@@ -1,63 +0,0 @@
/*
* SPDX-License-Identifier: MIT
*
* The MIT License (MIT)
*
* Copyright (c) 2015-2023 games647 and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit;
import java.net.InetAddress;
public final class InetUtils {
private InetUtils() {
// Utility
}
/**
* Verifies if the given IP address is from the local network
*
* @param address IP address
* @return true if address is from local network or even from the device itself (loopback)
*/
public static boolean isLocalAddress(InetAddress address) {
// Loopback addresses like 127.0.* (IPv4) or [::1] (IPv6)
return address.isLoopbackAddress()
// Example: 10.0.0.0, 172.16.0.0, 192.168.0.0, fec0::/10 (deprecated)
// Ref: https://en.wikipedia.org/wiki/IP_address#Private_addresses
|| address.isSiteLocalAddress()
// Example: 169.254.0.0/16, fe80::/10
// Ref: https://en.wikipedia.org/wiki/IP_address#Address_autoconfiguration
|| address.isLinkLocalAddress()
// non deprecated unique site-local that java doesn't check yet -> fc00::/7
|| isIPv6UniqueSiteLocal(address);
}
private static boolean isIPv6UniqueSiteLocal(InetAddress address) {
// ref: https://en.wikipedia.org/wiki/Unique_local_address
// currently undefined but could be used in the near future fc00::/8
return (address.getAddress()[0] & 0xFF) == 0xFC
// in use for unique site-local fd00::/8
|| (address.getAddress()[0] & 0xFF) == 0xFD;
}
}

View File

@@ -70,7 +70,7 @@ final class EncryptionUtil {
public static final int VERIFY_TOKEN_LENGTH = 4;
public static final String KEY_PAIR_ALGORITHM = "RSA";
private static final int RSA_LENGTH = 2_048;
private static final int RSA_LENGTH = 1_024;
private static final PublicKey MOJANG_SESSION_KEY;
private static final int LINE_LENGTH = 76;

View File

@@ -38,7 +38,6 @@ import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.Converters;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
@@ -47,7 +46,6 @@ import com.github.games647.craftapi.model.skin.SkinProperty;
import com.github.games647.craftapi.resolver.MojangResolver;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.InetUtils;
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
import lombok.val;
import org.bukkit.entity.Player;
@@ -73,17 +71,10 @@ public class VerifyResponseTask implements Runnable {
private static final String ENCRYPTION_CLASS_NAME = "MinecraftEncryption";
private static final Class<?> ENCRYPTION_CLASS;
private static final String ADDRESS_VERIFY_WARNING = "This indicates the use of reverse-proxy like HAProxy, "
+ "TCPShield, BungeeCord, Velocity, etc. "
+ "By default (configurable in the config) this plugin requests Mojang to verify the connecting IP "
+ "to this server with the one used to log into Minecraft to prevent MITM attacks. In "
+ "order to work this security feature, the actual client IP needs to be forwarding "
+ "(keyword IP forwarding). This process will also be useful for other server "
+ "features like IP banning, so that it doesn't ban the proxy IP.";
static {
ENCRYPTION_CLASS = MinecraftReflection.getMinecraftClass(
"util." + ENCRYPTION_CLASS_NAME, ENCRYPTION_CLASS_NAME
"util." + ENCRYPTION_CLASS_NAME, ENCRYPTION_CLASS_NAME
);
}
@@ -158,27 +149,10 @@ public class VerifyResponseTask implements Runnable {
} else {
//user tried to fake an authentication
disconnect(
"invalid-session",
"Session server rejected incoming connection for GameProfile {} ({}). Possible reasons are"
+ "1) Client IP address contacting Mojang and server during server join were different "
+ "(Do you use a reverse proxy? -> Enable IP forwarding, "
+ "or disable the feature in the config). "
+ "2) Player is offline, but tried to bypass the authentication"
+ "3) Client uses an outdated username for connecting (Fix: Restart client)",
requestedUsername, address
"invalid-session",
"GameProfile {} ({}) tried to log in with an invalid session. ServerId: {}",
session.getRequestUsername(), socketAddress, serverId
);
if (InetUtils.isLocalAddress(address)) {
plugin.getLog().warn(
"The incoming request for player {} uses a local IP address",
requestedUsername
);
plugin.getLog().warn(ADDRESS_VERIFY_WARNING);
} else {
plugin.getLog().warn("If you think this is an error, please verify that the incoming "
+ "IP address {} is not associated with a server hosting company.", address);
plugin.getLog().warn(ADDRESS_VERIFY_WARNING);
}
}
} catch (IOException ioEx) {
disconnect("error-kick", "Failed to connect to session server", ioEx);
@@ -203,7 +177,7 @@ public class VerifyResponseTask implements Runnable {
session.setVerified(true);
setPremiumUUID(session.getUuid());
receiveFakeStartPacket(realUsername, session.getClientPublicKey(), session.getUuid());
receiveFakeStartPacket(realUsername, session.getClientPublicKey());
}
private void setPremiumUUID(UUID premiumUUID) {
@@ -243,15 +217,15 @@ public class VerifyResponseTask implements Runnable {
try {
// Try to get the old (pre MC 1.16.4) encryption method
encryptMethod = FuzzyReflection.fromClass(networkManagerClass)
.getMethodByParameters("a", SecretKey.class);
.getMethodByParameters("a", SecretKey.class);
} catch (IllegalArgumentException exception) {
// Get the new encryption method
encryptMethod = FuzzyReflection.fromClass(networkManagerClass)
.getMethodByParameters("a", Cipher.class, Cipher.class);
.getMethodByParameters("a", Cipher.class, Cipher.class);
// Get the needed Cipher helper method (used to generate ciphers from login key)
cipherMethod = FuzzyReflection.fromClass(ENCRYPTION_CLASS)
.getMethodByParameters("a", int.class, Key.class);
.getMethodByParameters("a", int.class, Key.class);
}
}
@@ -294,23 +268,15 @@ public class VerifyResponseTask implements Runnable {
}
//fake a new login packet in order to let the server handle all the other stuff
private void receiveFakeStartPacket(String username, ClientPublicKey clientKey, UUID uuid) {
private void receiveFakeStartPacket(String username, ClientPublicKey clientKey) {
PacketContainer startPacket;
if (new MinecraftVersion(1, 20, 2).atOrAbove()) {
startPacket = new PacketContainer(START);
startPacket.getStrings().write(0, username);
startPacket.getUUIDs().write(0, uuid);
} else if (new MinecraftVersion(1, 19, 3).atOrAbove()) {
startPacket = new PacketContainer(START);
startPacket.getStrings().write(0, username);
startPacket.getOptionals(Converters.passthrough(UUID.class)).write(0, Optional.of(uuid));
} else if (new MinecraftVersion(1, 19, 0).atOrAbove()) {
if (new MinecraftVersion(1, 19, 0).atOrAbove()) {
startPacket = new PacketContainer(START);
startPacket.getStrings().write(0, username);
EquivalentConverter<WrappedProfileKeyData> converter = BukkitConverters.getWrappedPublicKeyDataConverter();
val wrappedKey = Optional.ofNullable(clientKey).map(key ->
new WrappedProfileKeyData(clientKey.expiry(), clientKey.key(), clientKey.signature())
new WrappedProfileKeyData(clientKey.expiry(), clientKey.key(), clientKey.signature())
);
startPacket.getOptionals(converter).write(0, wrappedKey);

View File

@@ -47,7 +47,7 @@
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<version>3.4.1</version>
<configuration>
<minimizeJar>true</minimizeJar>

View File

@@ -71,43 +71,25 @@ public class ConnectListener implements Listener {
private static final String UUID_FIELD_NAME = "uniqueId";
protected static final MethodHandle UNIQUE_ID_SETTER;
private static final String REWRITE_ID_NAME = "rewriteId";
protected static final MethodHandle REWRITE_ID_SETTER;
static {
MethodHandle uniqueIdHandle = null;
MethodHandle rewriterHandle = null;
MethodHandle setHandle = null;
try {
Lookup lookup = MethodHandles.lookup();
// test for implementation class availability
Class.forName("net.md_5.bungee.connection.InitialHandler");
uniqueIdHandle = getHandlerSetter(lookup, UUID_FIELD_NAME);
try {
rewriterHandle = getHandlerSetter(lookup, REWRITE_ID_NAME);
} catch (NoSuchFieldException noSuchFieldEx) {
Logger logger = LoggerFactory.getLogger(ConnectListener.class);
logger.error(
"Rewrite field not found. Setting only legacy BungeeCord field"
);
}
Field uuidField = InitialHandler.class.getDeclaredField(UUID_FIELD_NAME);
uuidField.setAccessible(true);
setHandle = lookup.unreflectSetter(uuidField);
} catch (ReflectiveOperationException reflectiveOperationException) {
Logger logger = LoggerFactory.getLogger(ConnectListener.class);
logger.error(
"Cannot find Bungee UUID field implementation; Disabling premium UUID and skin won't work.",
"Cannot find Bungee initial handler; Disabling premium UUID and skin won't work.",
reflectiveOperationException
);
}
UNIQUE_ID_SETTER = uniqueIdHandle;
REWRITE_ID_SETTER = rewriterHandle;
}
private static MethodHandle getHandlerSetter(Lookup lookup, String fieldName)
throws NoSuchFieldException, IllegalAccessException {
Field uuidField = InitialHandler.class.getDeclaredField(fieldName);
uuidField.setAccessible(true);
return lookup.unreflectSetter(uuidField);
UNIQUE_ID_SETTER = setHandle;
}
private final FastLoginBungee plugin;
@@ -197,12 +179,6 @@ public class ConnectListener implements Listener {
// So we have to do it with reflection
UNIQUE_ID_SETTER.invokeExact(connection, offlineUUID);
// if available set rewrite id to forward the UUID for newer BungeeCord versions since
// https://github.com/SpigotMC/BungeeCord/commit/1be25b6c74ec2be4b15adf8ca53a0497f01e2afe
if (REWRITE_ID_SETTER != null) {
REWRITE_ID_SETTER.invokeExact(connection, offlineUUID);
}
String format = "Overridden UUID from {} to {} (based of {}) on {}";
plugin.getLog().info(format, oldPremiumId, offlineUUID, username, connection);
} catch (Exception ex) {

View File

@@ -220,14 +220,6 @@
<property name="optional" value="true"/>
</module>
<!-- Suppress filters via comments -->
<!-- https://stackoverflow.com/a/4023351/9767089 -->
<module name="SuppressionCommentFilter">
<property name="offCommentFormat" value="CHECKSTYLE.OFF\: ([\w\|]+)"/>
<property name="onCommentFormat" value="CHECKSTYLE.ON\: ([\w\|]+)"/>
<property name="checkFormat" value="$1"/>
</module>
</module>
</module>

View File

@@ -63,23 +63,6 @@
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifestEntries>
<Automatic-Module-Name>com.github.games647.fastlogin.core</Automatic-Module-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!-- Libraries that we shade into the project -->
@@ -102,7 +85,7 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>2.0.11</version>
<version>2.0.7</version>
</dependency>
<!-- snakeyaml is present in Bungee, Spigot, Cauldron, so we could use this independent implementation -->
@@ -195,7 +178,7 @@
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
<version>2.10</version>
</dependency>
<dependency>

View File

@@ -25,17 +25,8 @@
*/
package com.github.games647.fastlogin.core.hooks;
/**
* Password generator for your auth plugin.
* @param <P> platform dependent player class
*/
@FunctionalInterface
public interface PasswordGenerator<P> {
/**
* Generate a password for a non-registered player
* @param player
* @return daw
*/
String getRandomPassword(P player);
}

View File

@@ -80,40 +80,6 @@ public abstract class FloodgateManagement<P extends C, C, L extends LoginSession
}
profile = core.getStorage().loadProfile(username);
if (profile.isSaved()) {
if (profile.isFloodgateMigrated()) {
if (profile.getFloodgate() == FloodgateState.TRUE && isLinked) {
core.getPlugin().getLog()
.info("Player {} is already stored by FastLogin as a non-linked Bedrock Edition player",
username);
return;
} else if (profile.getFloodgate() == FloodgateState.FALSE && isLinked) {
profile.setFloodgate(FloodgateState.LINKED);
core.getPlugin().getLog().info(
"Player {} will be changed from a Java player to a linked Floodgate player",
username);
}
} else {
if (isLinked) {
profile.setFloodgate(FloodgateState.LINKED);
core.getPlugin().getLog().info(
"Player {} will be migrated to the v2 database schema as a linked Floodgate user",
username);
} else {
profile.setFloodgate(FloodgateState.TRUE);
core.getPlugin().getLog().info(
"Player {} will be migrated to the v2 database schema as a Floodgate user", username);
}
}
} else {
if (isLinked) {
profile.setFloodgate(FloodgateState.LINKED);
} else {
profile.setFloodgate(FloodgateState.TRUE);
}
}
AuthPlugin<P> authPlugin = core.getAuthPluginHook();
try {
@@ -153,17 +119,13 @@ public abstract class FloodgateManagement<P extends C, C, L extends LoginSession
}
}
// defer auto registration, if it's not enabled in the config
if (!isRegistered && !isAutoAuthAllowed(autoRegisterFloodgate)) {
return;
}
// stop the auto login procedure, if the current connection's parameters don't match the one stored in our
// database
// ex. we stored a LINKED account, but the current connection is not linked
if ((profile.getFloodgate() == FloodgateState.LINKED && !isLinked)
|| (profile.getFloodgate() == FloodgateState.TRUE && isLinked)) {
return;
//logging in from bedrock for a second time threw an error with UUID
if (profile == null) {
profile = new StoredProfile(getUUID(player), username, true, getAddress(player).toString());
}
//start Bukkit/Bungee specific tasks

View File

@@ -1,86 +0,0 @@
/*
* SPDX-License-Identifier: MIT
*
* The MIT License (MIT)
*
* Copyright (c) 2015-2023 games647 and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.core.shared;
public enum FloodgateState {
/**
* Purely Java profile
*/
FALSE(0),
/**
* Purely Bedrock profile
*/
TRUE(1),
/**
* Bedrock profile is bidirectional associated with the Java Mojang profile.
*/
LINKED(2),
/**
* Data before floodgate database migration. Floodgate state is unknown.
*/
NOT_MIGRATED(3);
private int value;
FloodgateState(int value) {
this.value = value;
}
public int getValue() {
return value;
}
/**
* Convert a number to FloodgateState
* <ol start="0">
* <li>False</li>
* <li>True</li>
* <li>Linked</li>
* <li>Not Migrated</li>
* </ol>
* @param num the number, most likely loaded from the database
* @return FloodgateStatus on success, null otherwise
*/
public static FloodgateState fromInt(int num) {
// using Enum.values()[i] is expensive as per https://stackoverflow.com/a/8762387/9767089
switch (num) {
case 0:
return FloodgateState.FALSE;
case 1:
return FloodgateState.TRUE;
case 2:
return FloodgateState.LINKED;
case 3:
return FloodgateState.NOT_MIGRATED;
default:
return null;
}
}
}

View File

@@ -62,7 +62,7 @@ public abstract class ForceLoginManagement<P extends C, C, L extends LoginSessio
//premium player
AuthPlugin<P> authPlugin = core.getAuthPluginHook();
if (authPlugin == null) {
// maybe only bungeecord plugin
//maybe only bungeecord plugin
onForceActionSuccess(session);
} else {
boolean success = true;

View File

@@ -49,32 +49,17 @@ 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);
//check if the player is connecting through Bedrock Edition
if (bedrockService != null && bedrockService.isBedrockConnection(username)) {
//perform Bedrock specific checks and skip Java checks if no longer needed
if (bedrockService.performChecks(username, source)) {
return;
}
}
StoredProfile profile = core.getStorage().loadProfile(username);
//can't be a premium Java player, if it's not saved in the database
if (profile == null) {
return;
}
if (profile.isFloodgateMigrated()) {
if (profile.getFloodgate() == FloodgateState.TRUE) {
// migrated and enabled floodgate player, however the above bedrocks fails, so the current connection
// isn't premium
//check if the player is connecting through Bedrock Edition
if (bedrockService != null && bedrockService.isBedrockConnection(username)) {
//perform Bedrock specific checks and skip Java checks, if they are not needed
if (bedrockService.performChecks(username, source)) {
return;
}
} else {
profile.setFloodgate(FloodgateState.FALSE);
core.getPlugin().getLog().info(
"Player {} will be migrated to the v2 database schema as a JAVA user", username);
}
callFastLoginPreLoginEvent(username, source, profile);
@@ -154,12 +139,6 @@ public abstract class JoinManagement<P extends C, C, S extends LoginSource> {
if (core.getConfig().get("nameChangeCheck", false)) {
StoredProfile storedProfile = core.getStorage().loadProfile(profile.getId());
if (storedProfile != null) {
if (storedProfile.getFloodgate() == FloodgateState.TRUE) {
core.getPlugin().getLog()
.info("Player {} is already stored by FastLogin as a Bedrock Edition player.", username);
return false;
}
//uuid exists in the database
core.getPlugin().getLog().info("GameProfile {} changed it's username", profile);

View File

@@ -29,20 +29,9 @@ import com.github.games647.fastlogin.core.shared.LoginSession;
import com.github.games647.fastlogin.core.storage.StoredProfile;
/**
* This event fires if the plugin performs an auto login on the platform where the login plugin is
*
* {@snippet :
* @EventHandler()
* public void onPlayerLogin(FastLoginAutoLoginEvent loginEvent) {
* StoredProfile profile = loginEvent.getProfile();
* LoginSession session = loginEvent.getSession();
*
* System.out.println("Player: " + session.getUsername() + " is about to be force logged in");
* }
* }
* This event fires if the plugin performs an auto login on the platform where the login plugin is.
*/
public interface FastLoginAutoLoginEvent extends FastLoginCancellableEvent {
LoginSession getSession();
StoredProfile getProfile();
}

View File

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

View File

@@ -34,8 +34,6 @@ import com.github.games647.fastlogin.core.storage.StoredProfile;
public interface FastLoginPreLoginEvent {
String getUsername();
LoginSource getSource();
StoredProfile getProfile();
}

View File

@@ -33,7 +33,6 @@ import com.github.games647.fastlogin.core.storage.StoredProfile;
public interface FastLoginPremiumToggleEvent {
StoredProfile getProfile();
PremiumToggleReason getReason();
enum PremiumToggleReason {

View File

@@ -28,8 +28,6 @@ package com.github.games647.fastlogin.core.storage;
import com.github.games647.fastlogin.core.shared.PlatformPlugin;
import com.zaxxer.hikari.HikariConfig;
import java.util.Locale;
public class MySQLStorage extends SQLStorage {
private static final String JDBC_PROTOCOL = "jdbc:";
@@ -70,12 +68,12 @@ public class MySQLStorage extends SQLStorage {
}
private static String buildJDBCUrl(String driver, String host, int port, String database) {
MySQLVariant variant = MySQLVariant.fromDriver(driver);
if (variant == null) {
throw new IllegalArgumentException("Unknown storage driver");
String protocol = "mysql";
if (driver.contains("mariadb")) {
protocol = "mariadb";
}
return variant.getJdbcPrefix() + "://" + host + ':' + port + '/' + database;
return protocol + "://" + host + ':' + port + '/' + database;
}
private static void addPerformanceProperties(HikariConfig config) {
@@ -108,32 +106,4 @@ public class MySQLStorage extends SQLStorage {
// In our case it can be useful to see the time in error messages
// config.addDataSourceProperty("maintainTimeStats", false);
}
enum MySQLVariant {
MYSQL("mysql"),
MARIADB("mariadb");
private final String jdbcPrefix;
public static MySQLVariant fromDriver(String driver) {
String normalizedDriver = driver.toLowerCase(Locale.ENGLISH);
if (normalizedDriver.contains("mysql")) {
return MYSQL;
} else if (normalizedDriver.contains("mariadb")) {
return MARIADB;
}
return null;
}
MySQLVariant(String jdbcPrefix) {
this.jdbcPrefix = jdbcPrefix;
}
public String getJdbcPrefix() {
return jdbcPrefix;
}
}
}

View File

@@ -26,13 +26,11 @@
package com.github.games647.fastlogin.core.storage;
import com.github.games647.craftapi.UUIDAdapter;
import com.github.games647.fastlogin.core.shared.FloodgateState;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.slf4j.Logger;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -58,19 +56,13 @@ public abstract class SQLStorage implements AuthStorage {
+ "UNIQUE (`Name`) "
+ ')';
protected static final String ADD_FLOODGATE_COLUMN_STMT = "ALTER TABLE `" + PREMIUM_TABLE
+ "` ADD COLUMN `Floodgate` INTEGER(3)";
protected static final String LOAD_BY_NAME = "SELECT * FROM `" + PREMIUM_TABLE
+ "` WHERE `Name`=? LIMIT 1";
protected static final String LOAD_BY_UUID = "SELECT * FROM `" + PREMIUM_TABLE
+ "` WHERE `UUID`=? LIMIT 1";
protected static final String LOAD_BY_NAME = "SELECT * FROM `" + PREMIUM_TABLE + "` WHERE `Name`=? LIMIT 1";
protected static final String LOAD_BY_UUID = "SELECT * FROM `" + PREMIUM_TABLE + "` WHERE `UUID`=? LIMIT 1";
protected static final String INSERT_PROFILE = "INSERT INTO `" + PREMIUM_TABLE
+ "` (`UUID`, `Name`, `Premium`, `Floodgate`, `LastIp`) " + "VALUES (?, ?, ?, ?, ?) ";
+ "` (`UUID`, `Name`, `Premium`, `LastIp`) " + "VALUES (?, ?, ?, ?) ";
// limit not necessary here, because it's unique
protected static final String UPDATE_PROFILE = "UPDATE `" + PREMIUM_TABLE
+ "` SET `UUID`=?, `Name`=?, `Premium`=?, `Floodgate`=?, `LastIp`=?, "
+ "`LastLogin`=CURRENT_TIMESTAMP WHERE `UserID`=?";
+ "` SET `UUID`=?, `Name`=?, `Premium`=?, `LastIp`=?, `LastLogin`=CURRENT_TIMESTAMP WHERE `UserID`=?";
protected final Logger log;
protected final HikariDataSource dataSource;
@@ -89,23 +81,11 @@ public abstract class SQLStorage implements AuthStorage {
// 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
//todo: add unique uuid index usage
try (Connection con = dataSource.getConnection();
Statement stmt = con.createStatement()) {
stmt.executeUpdate(getCreateTableStmt());
// add Floodgate column
DatabaseMetaData md = con.getMetaData();
if (isColumnMissing(md, "Floodgate")) {
stmt.executeUpdate(ADD_FLOODGATE_COLUMN_STMT);
}
}
}
private boolean isColumnMissing(DatabaseMetaData metaData, String columnName) throws SQLException {
try (ResultSet rs = metaData.getColumns(null, null, PREMIUM_TABLE, columnName)) {
return !rs.next();
Statement createStmt = con.createStatement()) {
createStmt.executeUpdate(CREATE_TABLE_STMT);
}
}
@@ -117,8 +97,7 @@ public abstract class SQLStorage implements AuthStorage {
loadStmt.setString(1, name);
try (ResultSet resultSet = loadStmt.executeQuery()) {
return parseResult(resultSet).orElseGet(() -> new StoredProfile(null, name, false,
FloodgateState.FALSE, ""));
return parseResult(resultSet).orElseGet(() -> new StoredProfile(null, name, false, ""));
}
} catch (SQLException sqlEx) {
log.error("Failed to query profile: {}", name, sqlEx);
@@ -145,25 +124,15 @@ public abstract class SQLStorage implements AuthStorage {
private Optional<StoredProfile> parseResult(ResultSet resultSet) throws SQLException {
if (resultSet.next()) {
long userId = resultSet.getInt("UserID");
long userId = resultSet.getInt(1);
UUID uuid = Optional.ofNullable(resultSet.getString("UUID")).map(UUIDAdapter::parseId).orElse(null);
UUID uuid = Optional.ofNullable(resultSet.getString(2)).map(UUIDAdapter::parseId).orElse(null);
String name = resultSet.getString("Name");
boolean premium = resultSet.getBoolean("Premium");
int floodgateNum = resultSet.getInt("Floodgate");
FloodgateState floodgate;
// if the player wasn't migrated to the new database format
if (resultSet.wasNull()) {
floodgate = FloodgateState.NOT_MIGRATED;
} else {
floodgate = FloodgateState.fromInt(floodgateNum);
}
String lastIp = resultSet.getString("LastIp");
Instant lastLogin = resultSet.getTimestamp("LastLogin").toInstant();
return Optional.of(new StoredProfile(userId, uuid, name, premium, floodgate, lastIp, lastLogin));
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();
@@ -181,10 +150,9 @@ public abstract class SQLStorage implements AuthStorage {
saveStmt.setString(1, uuid);
saveStmt.setString(2, playerProfile.getName());
saveStmt.setBoolean(3, playerProfile.isPremium());
saveStmt.setInt(4, playerProfile.getFloodgate().getValue());
saveStmt.setString(5, playerProfile.getLastIp());
saveStmt.setString(4, playerProfile.getLastIp());
saveStmt.setLong(6, playerProfile.getRowId());
saveStmt.setLong(5, playerProfile.getRowId());
saveStmt.execute();
}
} else {
@@ -193,9 +161,7 @@ public abstract class SQLStorage implements AuthStorage {
saveStmt.setString(2, playerProfile.getName());
saveStmt.setBoolean(3, playerProfile.isPremium());
saveStmt.setBoolean(3, playerProfile.isPremium());
saveStmt.setInt(4, playerProfile.getFloodgate().getValue());
saveStmt.setString(5, playerProfile.getLastIp());
saveStmt.setString(4, playerProfile.getLastIp());
saveStmt.execute();
try (ResultSet generatedKeys = saveStmt.getGeneratedKeys()) {
@@ -213,14 +179,6 @@ public abstract class SQLStorage implements AuthStorage {
}
}
/**
* SQLite has a slightly different syntax, so this will be overridden by SQLiteStorage
* @return An SQL Statement to create the `premium` table
*/
protected String getCreateTableStmt() {
return CREATE_TABLE_STMT;
}
@Override
public void close() {
dataSource.close();

View File

@@ -31,23 +31,15 @@ import org.sqlite.JDBC;
import org.sqlite.SQLiteConfig;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SQLiteStorage extends SQLStorage {
protected static final String CREATE_TABLE_STMT = "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`) "
+ ')';
private static final String SQLITE_DRIVER = "org.sqlite.SQLiteDataSource";
private final Lock lock = new ReentrantLock();
@@ -111,9 +103,12 @@ public class SQLiteStorage extends SQLStorage {
}
@Override
protected String getCreateTableStmt() {
// SQLite has a different syntax for auto increment
return CREATE_TABLE_STMT.replace("AUTO_INCREMENT", "AUTOINCREMENT");
public void createTables() throws SQLException {
try (Connection con = dataSource.getConnection();
Statement createStmt = con.createStatement()) {
// SQLite has a different syntax for auto increment
createStmt.executeUpdate(CREATE_TABLE_STMT.replace("AUTO_INCREMENT", "AUTOINCREMENT"));
}
}
private static String replacePathVariables(Path dataFolder, String input) {

View File

@@ -26,7 +26,6 @@
package com.github.games647.fastlogin.core.storage;
import com.github.games647.craftapi.model.Profile;
import com.github.games647.fastlogin.core.shared.FloodgateState;
import java.time.Instant;
import java.util.Objects;
@@ -40,28 +39,20 @@ public class StoredProfile extends Profile {
private final ReentrantLock saveLock = new ReentrantLock();
private boolean premium;
private FloodgateState floodgate;
private String lastIp;
private Instant lastLogin;
public StoredProfile(long rowId, UUID uuid, String playerName, boolean premium, FloodgateState floodgate,
String lastIp, Instant lastLogin) {
public StoredProfile(long rowId, UUID uuid, String playerName, boolean premium, String lastIp, Instant lastLogin) {
super(uuid, playerName);
this.rowId = rowId;
this.premium = premium;
this.floodgate = floodgate;
this.lastIp = lastIp;
this.lastLogin = lastLogin;
}
public StoredProfile(UUID uuid, String playerName, boolean premium, FloodgateState isFloodgate, String lastIp) {
this(-1, uuid, playerName, premium, isFloodgate, lastIp, Instant.now());
}
@Deprecated
public StoredProfile(UUID uuid, String playerName, boolean premium, String lastIp) {
this(-1, uuid, playerName, premium, FloodgateState.FALSE, lastIp, Instant.now());
this(-1, uuid, playerName, premium, lastIp, Instant.now());
}
public ReentrantLock getSaveLock() {
@@ -105,18 +96,6 @@ public class StoredProfile extends Profile {
this.premium = premium;
}
public synchronized FloodgateState getFloodgate() {
return floodgate;
}
public synchronized boolean isFloodgateMigrated() {
return floodgate != FloodgateState.NOT_MIGRATED;
}
public synchronized void setFloodgate(FloodgateState floodgate) {
this.floodgate = floodgate;
}
public synchronized String getLastIp() {
return lastIp;
}
@@ -149,7 +128,7 @@ public class StoredProfile extends Profile {
}
return rowId == that.rowId && premium == that.premium
&& Objects.equals(lastIp, that.lastIp) && lastLogin.equals(that.lastLogin);
&& Objects.equals(lastIp, that.lastIp) && lastLogin.equals(that.lastLogin);
}
@Override
@@ -162,7 +141,6 @@ public class StoredProfile extends Profile {
return this.getClass().getSimpleName() + '{'
+ "rowId=" + rowId
+ ", premium=" + premium
+ ", floodgate=" + floodgate
+ ", lastIp='" + lastIp + '\''
+ ", lastLogin=" + lastLogin
+ "} " + super.toString();

View File

@@ -70,7 +70,7 @@ no-console: '&4You are not a player. You cannot toggle the premium state for YOU
# The user wants to toggle premium state, but BungeeCord support is enabled. This means the server have to communicate
# with the BungeeCord first which will establish a connection with the database server.
wait-on-proxy: '&6Sending request... (Do not forget to follow the BungeeCord setup guide referenced on the plugin page)'
wait-on-proxy: '&6Sending request... (Do not forget to follow the BungeeCord setup guide)'
# When ProtocolLib is enabled and the plugin is unable to continue handling a login request after a requested premium
# authentication. In this state the client expects a success packet with an encrypted connection or disconnect packet.
@@ -100,3 +100,5 @@ Type &a/premium&6 again to confirm'
invalid-public-key: '&cInvalid client public key. Please try to restart your game.'
# ========= Bungee/Waterfall only ================================

17
pom.xml
View File

@@ -103,7 +103,7 @@
<plugin>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>4.3</version>
<version>4.1</version>
<configuration>
<licenseSets>
<licenseSet>
@@ -115,7 +115,6 @@
<excludes>
<exclude>src/test/resources/**</exclude>
<exclude>src/main/resources/**</exclude>
<exclude>.java-version</exclude>
</excludes>
</licenseSet>
</licenseSets>
@@ -131,7 +130,7 @@
</plugin>
<plugin>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.3.1</version>
<version>3.1.2</version>
<configuration>
<configLocation>checkstyle.xml</configLocation>
<consoleOutput>true</consoleOutput>
@@ -142,7 +141,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.12.7</version>
<version>10.9.3</version>
</dependency>
</dependencies>
<executions>
@@ -159,7 +158,7 @@
<!-- Require newer versions for Junit5 support -->
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<version>2.22.2</version>
</plugin>
</plugins>
@@ -188,22 +187,22 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<version>1.18.26</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.1</version>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
<!-- Require inline to support static mocks -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.9.0</version>
<artifactId>mockito-inline</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
</dependencies>

View File

@@ -55,7 +55,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>templating-maven-plugin</artifactId>
<version>3.0.0</version>
<version>1.0.0</version>
<executions>
<execution>
<id>filter-src</id>
@@ -67,7 +67,7 @@
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<version>3.4.1</version>
<configuration>
<minimizeJar>true</minimizeJar>
@@ -97,10 +97,6 @@
<exclude>com.google.guava:guava</exclude>
</excludes>
</artifactSet>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
@@ -133,10 +129,6 @@
<id>velocity</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>opencollab-snapshot</id>
<url>https://repo.opencollab.dev/main/</url>
</repository>
</repositories>
<dependencies>
@@ -151,7 +143,7 @@
<dependency>
<groupId>com.velocitypowered</groupId>
<artifactId>velocity-api</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.1.1</version>
<scope>provided</scope>
</dependency>
@@ -159,29 +151,7 @@
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.3.2</version>
</dependency>
<!-- Bedrock player bridge -->
<dependency>
<groupId>org.geysermc.geyser</groupId>
<artifactId>core</artifactId>
<version>${geyser.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Floodgate -->
<dependency>
<groupId>org.geysermc.floodgate</groupId>
<artifactId>api</artifactId>
<version>${floodgate.version}</version>
<scope>provided</scope>
<version>3.1.0</version>
</dependency>
</dependencies>
</project>

View File

@@ -27,8 +27,6 @@ package com.github.games647.fastlogin.velocity;
import com.github.games647.fastlogin.core.AsyncScheduler;
import com.github.games647.fastlogin.core.hooks.bedrock.BedrockService;
import com.github.games647.fastlogin.core.hooks.bedrock.FloodgateService;
import com.github.games647.fastlogin.core.hooks.bedrock.GeyserService;
import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
import com.github.games647.fastlogin.core.message.ChannelMessage;
import com.github.games647.fastlogin.core.message.SuccessMessage;
@@ -52,8 +50,6 @@ import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import com.velocitypowered.api.proxy.messages.ChannelRegistrar;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.geyser.GeyserImpl;
import org.slf4j.Logger;
import java.io.IOException;
@@ -80,8 +76,6 @@ public class FastLoginVelocity implements PlatformPlugin<CommandSource> {
private FastLoginCore<Player, CommandSource, FastLoginVelocity> core;
private AsyncScheduler scheduler;
private FloodgateService floodgateService;
private GeyserService geyserService;
private UUID proxyId;
@Inject
@@ -101,14 +95,6 @@ public class FastLoginVelocity implements PlatformPlugin<CommandSource> {
return;
}
if (isPluginInstalled("floodgate")) {
floodgateService = new FloodgateService(FloodgateApi.getInstance(), core);
}
if (isPluginInstalled("Geyser-Velocity")) {
geyserService = new GeyserService(GeyserImpl.getInstance(), core);
}
server.getEventManager().register(this, new ConnectListener(this, core.getAntiBot()));
server.getEventManager().register(this, new PluginMessageListener(this));
@@ -154,21 +140,9 @@ public class FastLoginVelocity implements PlatformPlugin<CommandSource> {
return server.getPluginManager().isLoaded(name);
}
public FloodgateService getFloodgateService() {
return floodgateService;
}
public GeyserService getGeyserService() {
return geyserService;
}
@Override
public BedrockService<?> getBedrockService() {
if (floodgateService != null) {
return floodgateService;
}
return geyserService;
return null;
}
public FastLoginCore<Player, CommandSource, FastLoginVelocity> getCore() {

View File

@@ -53,12 +53,10 @@ public class VelocityLoginSource implements LoginSource {
public void kick(String message) {
if (message == null) {
preLoginEvent.setResult(PreLoginEvent.PreLoginComponentResult.denied(
Component.text("Kicked").color(NamedTextColor.WHITE))
);
Component.text("Kicked").color(NamedTextColor.WHITE)));
} else {
preLoginEvent.setResult(PreLoginEvent.PreLoginComponentResult.denied(
LegacyComponentSerializer.legacyAmpersand().deserialize(message))
);
LegacyComponentSerializer.legacyAmpersand().deserialize(message)));
}
}

View File

@@ -28,15 +28,13 @@ package com.github.games647.fastlogin.velocity.listener;
import com.github.games647.craftapi.UUIDAdapter;
import com.github.games647.fastlogin.core.antibot.AntiBotService;
import com.github.games647.fastlogin.core.antibot.AntiBotService.Action;
import com.github.games647.fastlogin.core.hooks.bedrock.FloodgateService;
import com.github.games647.fastlogin.core.shared.LoginSession;
import com.github.games647.fastlogin.core.storage.StoredProfile;
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.FloodgateAuthTask;
import com.github.games647.fastlogin.velocity.task.ForceLoginTask;
import com.velocitypowered.api.event.EventTask;
import com.velocitypowered.api.event.Continuation;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.PreLoginEvent;
@@ -50,7 +48,6 @@ import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.util.GameProfile.Property;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import java.net.InetSocketAddress;
import java.util.ArrayList;
@@ -70,9 +67,9 @@ public class ConnectListener {
}
@Subscribe
public EventTask onPreLogin(PreLoginEvent preLoginEvent) {
public void onPreLogin(PreLoginEvent preLoginEvent, Continuation continuation) {
if (!preLoginEvent.getResult().isAllowed()) {
return null;
return;
}
InboundConnection connection = preLoginEvent.getConnection();
@@ -84,30 +81,28 @@ public class ConnectListener {
switch (action) {
case Ignore:
// just ignore
return null;
return;
case Block:
String message = plugin.getCore().getMessage("kick-antibot");
TextComponent messageParsed = LegacyComponentSerializer.legacyAmpersand().deserialize(message);
PreLoginComponentResult reason = PreLoginComponentResult.denied(messageParsed);
preLoginEvent.setResult(reason);
return null;
break;
case Continue:
default:
return EventTask.async(
new AsyncPremiumCheck(plugin, connection, username, preLoginEvent)
Runnable asyncPremiumCheck = new AsyncPremiumCheck(
plugin, connection, username, continuation, preLoginEvent
);
plugin.getScheduler().runAsync(asyncPremiumCheck);
break;
}
}
@Subscribe
public void onGameProfileRequest(GameProfileRequestEvent event) {
public void onGameprofileRequest(GameProfileRequestEvent event) {
if (event.isOnlineMode()) {
LoginSession session = plugin.getSession().get(event.getConnection().getRemoteAddress());
if (session == null) {
plugin.getLog().warn("No active login session found for player {}", event.getUsername());
return;
}
UUID verifiedUUID = event.getGameProfile().getId();
String verifiedUsername = event.getUsername();
@@ -146,20 +141,8 @@ public class ConnectListener {
Player player = serverConnectedEvent.getPlayer();
RegisteredServer server = serverConnectedEvent.getServer();
FloodgateService floodgateService = plugin.getFloodgateService();
if (floodgateService != null) {
FloodgatePlayer floodgatePlayer = floodgateService.getBedrockPlayer(player.getUniqueId());
if (floodgatePlayer != null) {
plugin.getLog().info("Running floodgate handling for {}", player);
Runnable floodgateAuthTask = new FloodgateAuthTask(plugin.getCore(), player, floodgatePlayer, server);
plugin.getScheduler().runAsync(floodgateAuthTask);
return;
}
}
VelocityLoginSession session = plugin.getSession().get(player.getRemoteAddress());
if (session == null) {
plugin.getLog().info("No active login session found on server connect for {}", player);
return;
}
@@ -168,7 +151,7 @@ public class ConnectListener {
// player is still in the login phase and reported to be offline.
Runnable loginTask = new ForceLoginTask(plugin.getCore(), player, server, session);
plugin.getProxy().getScheduler()
.buildTask(plugin, loginTask)
.buildTask(plugin, () -> plugin.getScheduler().runAsync(loginTask))
.delay(1L, TimeUnit.SECONDS) // Delay at least one second, otherwise the login command can be missed
.schedule();
}

View File

@@ -29,7 +29,6 @@ import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
import com.github.games647.fastlogin.core.message.SuccessMessage;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.hooks.bedrock.FloodgateService;
import com.github.games647.fastlogin.velocity.FastLoginVelocity;
import com.github.games647.fastlogin.velocity.VelocityLoginSession;
import com.github.games647.fastlogin.velocity.task.AsyncToggleMessage;
@@ -116,15 +115,7 @@ public class PluginMessageListener {
}
private void onSuccessMessage(Player forPlayer) {
boolean shouldPersist = forPlayer.isOnlineMode();
FloodgateService floodgateService = plugin.getFloodgateService();
if (!shouldPersist && floodgateService != null) {
// always save floodgate players to lock this username
shouldPersist = floodgateService.isBedrockPlayer(forPlayer.getUniqueId());
}
if (shouldPersist) {
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());

View File

@@ -33,6 +33,7 @@ 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;
@@ -44,22 +45,28 @@ public class AsyncPremiumCheck extends JoinManagement<Player, CommandSource, Vel
private final FastLoginVelocity plugin;
private final String username;
private final Continuation continuation;
private final PreLoginEvent preLoginEvent;
private final InboundConnection connection;
public AsyncPremiumCheck(FastLoginVelocity plugin, InboundConnection connection, String username,
PreLoginEvent preLoginEvent) {
Continuation continuation, PreLoginEvent preLoginEvent) {
super(plugin.getCore(), plugin.getCore().getAuthPluginHook(), plugin.getBedrockService());
this.plugin = plugin;
this.connection = connection;
this.username = username;
this.continuation = continuation;
this.preLoginEvent = preLoginEvent;
}
@Override
public void run() {
plugin.getSession().remove(connection.getRemoteAddress());
super.onLogin(username, new VelocityLoginSource(connection, preLoginEvent));
try {
super.onLogin(username, new VelocityLoginSource(connection, preLoginEvent));
} finally {
continuation.resume();
}
}
@Override

View File

@@ -1,85 +0,0 @@
/*
* SPDX-License-Identifier: MIT
*
* The MIT License (MIT)
*
* Copyright (c) 2015-2023 games647 and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.velocity.task;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.shared.FloodgateManagement;
import com.github.games647.fastlogin.velocity.FastLoginVelocity;
import com.github.games647.fastlogin.velocity.VelocityLoginSession;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import java.net.InetSocketAddress;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class FloodgateAuthTask
extends FloodgateManagement<Player, CommandSource, VelocityLoginSession, FastLoginVelocity> {
private final RegisteredServer server;
public FloodgateAuthTask(FastLoginCore<Player, CommandSource, FastLoginVelocity> core, Player player,
FloodgatePlayer floodgatePlayer, RegisteredServer server) {
super(core, player, floodgatePlayer);
this.server = server;
}
@Override
protected void startLogin() {
VelocityLoginSession session = new VelocityLoginSession(player.getUsername(), isRegistered, profile);
core.getPlugin().getSession().put(player.getRemoteAddress(), session);
// enable auto login based on the value of 'autoLoginFloodgate' in config.yml
boolean forcedOnlineMode = autoLoginFloodgate.equals("true")
|| (autoLoginFloodgate.equals("linked") && isLinked);
// 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(core.getPlugin().getCore(), player, server, session, forcedOnlineMode);
core.getPlugin().getProxy().getScheduler()
.buildTask(core.getPlugin(), () -> core.getPlugin().getScheduler().runAsync(loginTask))
.delay(1L, TimeUnit.SECONDS) // Delay at least one second, otherwise the login command can be missed
.schedule();
}
@Override
protected String getName(Player player) {
return player.getUsername();
}
@Override
protected UUID getUUID(Player player) {
return player.getUniqueId();
}
@Override
protected InetSocketAddress getAddress(Player player) {
return player.getRemoteAddress();
}
}

View File

@@ -68,7 +68,6 @@ public class ForceLoginTask
@Override
public void run() {
if (session == null) {
core.getPlugin().getLog().info("No active login session on force handling for {}", player);
return;
}
@@ -81,7 +80,6 @@ public class ForceLoginTask
@Override
public boolean forceLogin(Player player) {
if (session.isAlreadyLogged()) {
core.getPlugin().getLog().info("Ignoring second force login attempt for {}", player);
return true;
}
@@ -94,11 +92,11 @@ public class ForceLoginTask
VelocityFastLoginAutoLoginEvent event = new VelocityFastLoginAutoLoginEvent(session, profile);
try {
return core.getPlugin().getProxy().getEventManager().fire(event).get();
} catch (InterruptedException interruptedEx) {
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Set the interrupt flag again
return event;
} catch (ExecutionException executionEx) {
core.getPlugin().getLog().error("Error firing event", executionEx);
} catch (ExecutionException e) {
core.getPlugin().getLog().error("Error firing event", e);
return event;
}
}
@@ -116,8 +114,6 @@ public class ForceLoginTask
type = Type.REGISTER;
}
core.getPlugin().getLog().info("Sending force {} for {} towards server {}", type, player.getUsername(), server);
UUID proxyId = core.getPlugin().getProxyId();
ChannelMessage loginMessage = new LoginActionMessage(type, player.getUsername(), proxyId);
core.getPlugin().sendPluginMessage(server, loginMessage);