Complete private authentication, server integrations and activity reporting

Add Fabric/Forge version builds and Paper integration, preserve permanent player identities across renames, harden session authorization, and surface privacy-conscious launcher/server activity in the panel.
This commit is contained in:
SCOPEDD committed 2026-09-28 13:31:17 -04:00
commit a1f19e86c6
74 files changed
+2108 -95

No files matched your search

@@ -0,0 +1,130 @@
package net.scopenet.paper;
import net.scopenet.integration.*;
import org.bukkit.*;
import org.bukkit.entity.Player;
import org.bukkit.event.*;
import org.bukkit.event.block.*;
import org.bukkit.event.entity.*;
import org.bukkit.event.player.*;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.concurrent.TimeUnit;
public final class ScopenetPlugin extends JavaPlugin implements Listener {
private Integration integration;
@Override public void onEnable() {
// Install the deny-by-default listener even if configuration is invalid.
getServer().getPluginManager().registerEvents(this, this);
saveDefaultConfig();
try {
integration = new Integration(
Settings.of(getConfig().getString("panel-url", ""), getConfig().getString("token", "")),
getServer().getName(), Bukkit.getBukkitVersion().split("-")[0], getDescription().getVersion(),
Bukkit.getOnlineMode(), Bukkit.getMaxPlayers(), getLogger()::warning,
(id, message) -> {
if (!isEnabled()) return;
Bukkit.getScheduler().runTask(this, () -> {
Player player = Bukkit.getPlayer(id);
if (player != null) player.kickPlayer(message);
});
});
Bukkit.getScheduler().runTaskTimer(this, integration::tick, 1, 1);
// Reloading an auth plugin cannot silently admit existing sessions.
for (Player player : Bukkit.getOnlinePlayers()) player.kickPlayer("SCOPENET restarted. Please reconnect.");
} catch (Exception e) {
getLogger().severe("SCOPENET is blocking joins: " + e.getMessage());
for (Player player : Bukkit.getOnlinePlayers()) player.kickPlayer(Integration.UNAVAILABLE);
}
}
@Override public void onDisable() {
if (integration != null) integration.close();
}
@EventHandler(priority = EventPriority.HIGHEST)
public void login(AsyncPlayerPreLoginEvent event) {
if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) return;
String denial = Integration.UNAVAILABLE;
if (integration != null) {
try {
denial = integration.login(event.getUniqueId(), event.getName(), event.getAddress().getHostAddress())
.get(7, TimeUnit.SECONDS);
} catch (InterruptedException e) { Thread.currentThread().interrupt(); }
catch (Exception ignored) { /* fail closed */ }
}
if (denial != null) event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, denial);
}
@EventHandler(priority = EventPriority.MONITOR)
public void join(PlayerJoinEvent event) {
if (integration != null) integration.activity.join(event.getPlayer().getUniqueId(), event.getPlayer().getName());
}
@EventHandler(priority = EventPriority.MONITOR)
public void leave(PlayerQuitEvent event) {
if (integration != null) integration.activity.leave(event.getPlayer().getUniqueId(), event.getPlayer().getName());
}
private void add(Player player, String stat) {
if (integration != null) integration.activity.add(player.getUniqueId(), player.getName(), stat, 1);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void broken(BlockBreakEvent event) { add(event.getPlayer(), "blocks_broken"); }
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void placed(BlockPlaceEvent event) { add(event.getPlayer(), "blocks_placed"); }
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void chat(AsyncPlayerChatEvent event) { add(event.getPlayer(), "messages"); }
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void command(PlayerCommandPreprocessEvent event) {
audit(event.getPlayer(), "command", event.getMessage().strip().split("\\s+", 2)[0]);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void statistic(PlayerStatisticIncrementEvent event) {
if (integration == null) return;
String item = event.getMaterial() != null ? ":" + event.getMaterial().name()
: event.getEntityType() != null ? ":" + event.getEntityType().name() : "";
integration.activity.action(event.getPlayer().getUniqueId(), event.getPlayer().getName(),
event.getStatistic().name() + item, event.getNewValue() - event.getPreviousValue());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void inventory(InventoryClickEvent event) {
if (event.getWhoClicked() instanceof Player player)
audit(player, "inventory", event.getAction().name() + " slot=" + event.getRawSlot()
+ (event.getCurrentItem() == null ? "" : " item=" + event.getCurrentItem().getType().name()));
}
@EventHandler(priority = EventPriority.MONITOR)
public void advancement(PlayerAdvancementDoneEvent event) {
audit(event.getPlayer(), "advancement", event.getAdvancement().getKey().toString());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void teleport(PlayerTeleportEvent event) {
if (event.getTo() != null) audit(event.getPlayer(), "teleport", event.getCause().name() + " → "
+ event.getTo().getWorld().getName() + " " + event.getTo().getBlockX() + ","
+ event.getTo().getBlockY() + "," + event.getTo().getBlockZ());
}
private void audit(Player player, String kind, String detail) {
if (integration != null) integration.activity.event(player.getUniqueId(), player.getName(), kind, detail);
}
@EventHandler(priority = EventPriority.MONITOR)
public void death(EntityDeathEvent event) {
if (event.getEntity() instanceof Player player) {
add(player, "deaths");
if (integration != null) integration.activity.event(player.getUniqueId(), player.getName(), "death",
event instanceof PlayerDeathEvent death ? death.getDeathMessage() : null);
}
Player killer = event.getEntity().getKiller();
if (killer != null) add(killer, event.getEntity() instanceof Player ? "player_kills" : "mob_kills");
}
}
@@ -0,0 +1,3 @@
# Create a server under Servers in the panel, then paste its token here.
panel-url: "https://panel.example.com"
token: ""
@@ -0,0 +1,6 @@
name: SCOPENET
version: '${version}'
main: net.scopenet.paper.ScopenetPlugin
api-version: '1.20'
description: SCOPENET private authentication, access control and activity reporting.
load: STARTUP