added website endpoint to LoginLog for reporting signup and login IPs
This commit is contained in:
1 parent
490d8b9891
commit
095db7e796
2 files changed
+41
-7
No files matched your search
+1
@@ -19,6 +19,7 @@ record LoginAttempt(
|
|||||||
) {
|
) {
|
||||||
static final String STAGE_PRE_LOGIN = "PRE_LOGIN";
|
static final String STAGE_PRE_LOGIN = "PRE_LOGIN";
|
||||||
static final String STAGE_LOGIN = "LOGIN";
|
static final String STAGE_LOGIN = "LOGIN";
|
||||||
|
static final String STAGE_WEBSITE = "WEBSITE";
|
||||||
static final String RESULT_ABORTED = "ABORTED";
|
static final String RESULT_ABORTED = "ABORTED";
|
||||||
|
|
||||||
static String ipOf(@Nullable InetAddress address) {
|
static String ipOf(@Nullable InetAddress address) {
|
||||||
|
|||||||
+40
-7
@@ -1,14 +1,16 @@
|
|||||||
package eu.mhsl.craftattack.spawn.common.appliances.tooling.loginLog;
|
package eu.mhsl.craftattack.spawn.common.appliances.tooling.loginLog;
|
||||||
|
|
||||||
|
import com.google.common.net.InetAddresses;
|
||||||
import eu.mhsl.craftattack.spawn.core.Main;
|
import eu.mhsl.craftattack.spawn.core.Main;
|
||||||
|
import eu.mhsl.craftattack.spawn.core.api.server.HttpServer;
|
||||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||||
import eu.mhsl.craftattack.spawn.core.database.Database;
|
import eu.mhsl.craftattack.spawn.core.database.Database;
|
||||||
|
import eu.mhsl.craftattack.spawn.core.util.server.Floodgate;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.scheduler.BukkitTask;
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
import org.geysermc.floodgate.api.FloodgateApi;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@@ -20,6 +22,7 @@ import java.time.LocalDateTime;
|
|||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
@@ -27,6 +30,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
/**
|
/**
|
||||||
* Records every login attempt (allowed or not) with its IP address in the table login_attempts
|
* Records every login attempt (allowed or not) with its IP address in the table login_attempts
|
||||||
* (schema: resources/migrations/loginLog). Basis for detecting ban evasion.
|
* (schema: resources/migrations/loginLog). Basis for detecting ban evasion.
|
||||||
|
* The website reports signups and logins of its users via POST /api/loginlog/website.
|
||||||
* Never blocks or delays a login: writes run on the database thread, failures are only logged.
|
* Never blocks or delays a login: writes run on the database thread, failures are only logged.
|
||||||
*/
|
*/
|
||||||
public class LoginLog extends Appliance {
|
public class LoginLog extends Appliance {
|
||||||
@@ -68,7 +72,7 @@ public class LoginLog extends Appliance {
|
|||||||
void onPreLogin(UUID uuid, String name, @Nullable InetAddress address, String result, @Nullable Component kickMessage) {
|
void onPreLogin(UUID uuid, String name, @Nullable InetAddress address, String result, @Nullable Component kickMessage) {
|
||||||
if(!this.database.isAvailable()) return;
|
if(!this.database.isAvailable()) return;
|
||||||
LoginAttempt attempt = new LoginAttempt(
|
LoginAttempt attempt = new LoginAttempt(
|
||||||
Instant.now(), uuid, name, LoginAttempt.ipOf(address), this.isBedrock(uuid),
|
Instant.now(), uuid, name, LoginAttempt.ipOf(address), Floodgate.isFloodgateUuid(uuid),
|
||||||
LoginAttempt.STAGE_PRE_LOGIN, result, plainText(kickMessage)
|
LoginAttempt.STAGE_PRE_LOGIN, result, plainText(kickMessage)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -82,7 +86,7 @@ public class LoginLog extends Appliance {
|
|||||||
LoginAttempt pending = this.pendingAttempts.remove(uuid);
|
LoginAttempt pending = this.pendingAttempts.remove(uuid);
|
||||||
LoginAttempt attempt = pending != null
|
LoginAttempt attempt = pending != null
|
||||||
? pending
|
? pending
|
||||||
: new LoginAttempt(Instant.now(), uuid, name, LoginAttempt.ipOf(address), this.isBedrock(uuid), "", "", null);
|
: new LoginAttempt(Instant.now(), uuid, name, LoginAttempt.ipOf(address), Floodgate.isFloodgateUuid(uuid), "", "", null);
|
||||||
|
|
||||||
this.write(attempt.withOutcome(LoginAttempt.STAGE_LOGIN, result, plainText(kickMessage)));
|
this.write(attempt.withOutcome(LoginAttempt.STAGE_LOGIN, result, plainText(kickMessage)));
|
||||||
}
|
}
|
||||||
@@ -124,12 +128,41 @@ public class LoginLog extends Appliance {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isBedrock(UUID uuid) {
|
private record WebsiteEvent(String event, String uuid, String username, String ip) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recordWebsiteEvent(WebsiteEvent request) {
|
||||||
|
if(request == null) throw new IllegalArgumentException("Request body missing");
|
||||||
|
if(!this.database.isAvailable()) throw new IllegalStateException("No database configured");
|
||||||
|
|
||||||
|
String event = request.event() == null ? "" : request.event().trim().toUpperCase(Locale.ROOT);
|
||||||
|
if(!event.equals("SIGNUP") && !event.equals("LOGIN"))
|
||||||
|
throw new IllegalArgumentException("Field 'event' must be 'signup' or 'login'");
|
||||||
|
if(request.uuid() == null || request.uuid().isBlank())
|
||||||
|
throw new IllegalArgumentException("Field 'uuid' is required");
|
||||||
|
UUID uuid;
|
||||||
try {
|
try {
|
||||||
return FloodgateApi.getInstance().isFloodgatePlayer(uuid);
|
uuid = UUID.fromString(request.uuid().trim());
|
||||||
} catch(Throwable e) {
|
} catch(IllegalArgumentException e) {
|
||||||
return false;
|
throw new IllegalArgumentException("Field 'uuid' is not a valid UUID");
|
||||||
}
|
}
|
||||||
|
if(request.username() == null || request.username().isBlank())
|
||||||
|
throw new IllegalArgumentException("Field 'username' is required");
|
||||||
|
if(request.ip() == null || !InetAddresses.isInetAddress(request.ip().trim()))
|
||||||
|
throw new IllegalArgumentException("Field 'ip' is not a valid IP address");
|
||||||
|
|
||||||
|
this.write(new LoginAttempt(
|
||||||
|
Instant.now(), uuid, request.username().trim(), request.ip().trim(), Floodgate.isFloodgateUuid(uuid),
|
||||||
|
LoginAttempt.STAGE_WEBSITE, event, null
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void httpApi(HttpServer.ApiBuilder apiBuilder) {
|
||||||
|
apiBuilder.post("website", WebsiteEvent.class, (request, original) -> {
|
||||||
|
this.recordWebsiteEvent(request);
|
||||||
|
return HttpServer.nothing;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @Nullable String plainText(@Nullable Component component) {
|
private static @Nullable String plainText(@Nullable Component component) {
|
||||||
|
|||||||
Reference in new issue
Block a user