added connection key and recorded event to LoginLog

This commit is contained in:
MineTec committed 2026-10-14 17:00:11 +02:00
1 parent 095db7e796
commit 86ae261c60
4 files changed
+148 -6

No files matched your search

@@ -5,6 +5,8 @@ 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.database.Database;
import eu.mhsl.craftattack.spawn.core.event.LoginAttemptRecordedEvent;
import eu.mhsl.craftattack.spawn.core.util.network.Networks;
import eu.mhsl.craftattack.spawn.core.util.server.Floodgate;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
@@ -16,6 +18,8 @@ import org.jetbrains.annotations.Nullable;
import java.net.InetAddress;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
@@ -30,6 +34,7 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* 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.
* After each insert a LoginAttemptRecordedEvent is fired on the database thread.
* 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.
*/
@@ -101,19 +106,29 @@ public class LoginLog extends Appliance {
}
private void write(LoginAttempt attempt) {
String sql = "INSERT INTO login_attempts (at, uuid, name, ip, bedrock, stage, result, reason) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
String network = Networks.networkOf(attempt.ip());
String sql = "INSERT INTO login_attempts (at, uuid, name, ip, network, bedrock, stage, result, reason) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
this.database.execute("recording login attempt of " + attempt.name(), connection -> {
try(PreparedStatement statement = connection.prepareStatement(sql)) {
long id;
try(PreparedStatement statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
statement.setObject(1, LocalDateTime.ofInstant(attempt.at(), ZoneOffset.UTC));
statement.setString(2, attempt.uuid().toString());
statement.setString(3, attempt.name());
statement.setString(4, attempt.ip());
statement.setBoolean(5, attempt.bedrock());
statement.setString(6, attempt.stage());
statement.setString(7, attempt.result());
statement.setString(8, attempt.reason());
statement.setString(5, network);
statement.setBoolean(6, attempt.bedrock());
statement.setString(7, attempt.stage());
statement.setString(8, attempt.result());
statement.setString(9, attempt.reason());
statement.executeUpdate();
try(ResultSet keys = statement.getGeneratedKeys()) {
if(!keys.next()) return;
id = keys.getLong(1);
}
}
Bukkit.getPluginManager().callEvent(new LoginAttemptRecordedEvent(new LoginAttemptRecordedEvent.RecordedAttempt(
id, attempt.at(), attempt.uuid(), attempt.name(), attempt.ip(), network, attempt.stage(), attempt.result()
)));
});
}
@@ -0,0 +1,6 @@
-- Connection key per attempt: the IPv4 address or the IPv6 /64 prefix. Groups accounts by internet connection.
ALTER TABLE login_attempts
ADD COLUMN network VARCHAR(45) NULL COMMENT 'IPv4 address or IPv6 /64 prefix, NULL if unknown' AFTER ip,
ADD KEY idx_network_at (network, at);
UPDATE login_attempts SET network = ip WHERE ip REGEXP '^[0-9]{1,3}([.][0-9]{1,3}){3}$';