Initial commit of plugin files

This commit is contained in:
AdzelFirestar
2025-06-09 16:52:58 -07:00
parent cb5805c6da
commit e300b22d87
25 changed files with 1234 additions and 0 deletions

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "automatic"
}

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.adzel.addyauthreborn</groupId>
<artifactId>AddyAuthReborn</artifactId>
<name>AddyAuth Reborn</name>
<version>0.1-pre</version>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
<relocations>
<relocation>
<pattern>at.favre.lib</pattern>
<shadedPattern>com.adzel.addyauthreborn.shaded.bcrypt</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>papermc-repo</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>sonatype-oss</id>
<url>https://oss.sonatype.org/content/repositories/releases/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.20.4-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<bcrypt.version>0.9.0</bcrypt.version>
<java.version>17</java.version>
<paper.version>1.20.4-R0.1-SNAPSHOT</paper.version>
<mariadb.version>3.3.3</mariadb.version>
</properties>
</project>

103
pom.xml Normal file
View File

@ -0,0 +1,103 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.adzel.addyauthreborn</groupId>
<artifactId>AddyAuthReborn</artifactId>
<version>0.1-pre</version>
<name>AddyAuth Reborn</name>
<properties>
<java.version>17</java.version>
<paper.version>1.20.4-R0.1-SNAPSHOT</paper.version>
<bcrypt.version>0.9.0</bcrypt.version>
<mariadb.version>3.3.3</mariadb.version>
</properties>
<repositories>
<repository>
<id>papermc-repo</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>sonatype-oss</id>
<url>https://oss.sonatype.org/content/repositories/releases/</url>
</repository>
</repositories>
<dependencies>
<!-- Paper API -->
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>${paper.version}</version>
<scope>provided</scope>
</dependency>
<!-- Favre BCrypt -->
<dependency>
<groupId>at.favre.lib</groupId>
<artifactId>bcrypt</artifactId>
<version>${bcrypt.version}</version>
</dependency>
<!-- MariaDB JDBC Driver -->
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>${mariadb.version}</version>
</dependency>
<!-- Adventure for MiniMessage -->
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-platform-bukkit</artifactId>
<version>4.3.2</version>
</dependency>
<!-- HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.1.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Java Compiler -->
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<!-- Shade Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<minimizeJar>true</minimizeJar>
<relocations>
<!-- OPTIONAL: Only add this if you want to shade bcrypt -->
<relocation>
<pattern>at.favre.lib</pattern>
<shadedPattern>com.adzel.addyauthreborn.shaded.bcrypt</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,117 @@
package com.adzel.addyauthreborn;
import com.adzel.addyauthreborn.auth.AuthManager;
import com.adzel.addyauthreborn.commands.LoginCommand;
import com.adzel.addyauthreborn.commands.LockCommand;
import com.adzel.addyauthreborn.commands.RegisterCommand;
import com.adzel.addyauthreborn.commands.AddyAuthCommand;
import com.adzel.addyauthreborn.listeners.LockListeners;
import com.adzel.addyauthreborn.listeners.AdminJoinListener;
import com.adzel.addyauthreborn.listeners.ConnectionListener;
import com.adzel.addyauthreborn.lock.AFKMonitor;
import com.adzel.addyauthreborn.lock.OfflineLockManager;
import com.adzel.addyauthreborn.updates.UpdateChecker;
import com.adzel.addyauthreborn.utils.DatabaseUtil;
import com.zaxxer.hikari.HikariDataSource;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import org.bukkit.plugin.java.JavaPlugin;
public class AddyAuthReborn extends JavaPlugin {
private static AddyAuthReborn instance;
private AuthManager authManager;
private AFKMonitor afkMonitor;
private OfflineLockManager offlineLockManager;
private BukkitAudiences adventure;
private boolean updateAvailable = false;
@Override
public void onEnable() {
instance = this;
saveDefaultConfig();
adventure = BukkitAudiences.create(this);
if (!DatabaseUtil.initDatabase()) {
getLogger().severe("Failed to initialize database. Disabling plugin.");
getServer().getPluginManager().disablePlugin(this);
return;
}
authManager = new AuthManager();
afkMonitor = new AFKMonitor(this);
offlineLockManager = new OfflineLockManager(this);
// Commands
getCommand("register").setExecutor(new RegisterCommand(this));
getCommand("login").setExecutor(new LoginCommand(this));
getCommand("lock").setExecutor(new LockCommand(this));
getCommand("addyauth").setExecutor(new AddyAuthCommand(this));
// Listeners
getServer().getPluginManager().registerEvents(new LockListeners(this), this);
getServer().getPluginManager().registerEvents(new AdminJoinListener(this), this);
getServer().getPluginManager().registerEvents(new ConnectionListener(this), this); // PlayerJoin/Leave for tracking AFK state
// Update check
new UpdateChecker(this, "https://git.adzeldevelops.site/api/v1/repos/AdzelFirestar/AddyAuthReborn/releases/latest")
.getLatestVersion(latest -> {
String current = getDescription().getVersion();
if (!current.equalsIgnoreCase(latest)) {
updateAvailable = true;
getLogger().warning("A new version of AddyAuth Reborn is available: " + latest);
}
});
getLogger().info("AddyAuth Reborn enabled successfully.");
}
@Override
public void onDisable() {
if (adventure != null) {
adventure.close();
}
if (afkMonitor != null) {
afkMonitor.shutdown(); // 🧼 clean up task
}
DatabaseUtil.shutdown();
getLogger().info("AddyAuth Reborn has been disabled.");
}
public static AddyAuthReborn getInstance() {
return instance;
}
public AuthManager getAuthManager() {
return authManager;
}
public AFKMonitor getAFKMonitor() {
return afkMonitor;
}
public OfflineLockManager getOfflineLockManager() {
return offlineLockManager;
}
public HikariDataSource getDataSource() {
return DatabaseUtil.getDataSource();
}
public boolean isUpdateAvailable() {
return updateAvailable;
}
public int getMinPasswordLength() {
return getConfig().getInt("General Settings.Minimum Password Length", 5);
}
public int getMaxPasswordLength() {
return getConfig().getInt("General Settings.Maximum Password Length", 15);
}
public BukkitAudiences adventure() {
return adventure;
}
}

View File

@ -0,0 +1,61 @@
package com.adzel.addyauthreborn.auth;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import at.favre.lib.crypto.bcrypt.BCrypt;
public class AuthManager {
private final AuthStorage storage;
private final Set<UUID> lockedPlayers = ConcurrentHashMap.newKeySet();
public AuthManager() {
this.storage = new AuthStorage(); // will handle DB interactions
}
public boolean isRegistered(UUID uuid) {
return storage.isRegistered(uuid);
}
public boolean registerPassword(UUID uuid, String plainPassword) {
if (isRegistered(uuid)) return false;
String hash = BCrypt.withDefaults().hashToString(12, plainPassword.toCharArray());
return storage.savePassword(uuid, hash);
}
public boolean verifyPassword(UUID uuid, String attempt) {
String hash = storage.getPasswordHash(uuid);
if (hash != null) {
BCrypt.Result result = BCrypt.verifyer().verify(attempt.toCharArray(), hash);
if (result.verified) {
unlock(uuid); // unlock on successful login
return true;
}
}
return false;
}
public void lock(UUID uuid) {
lockedPlayers.add(uuid);
storage.setLastLockTime(uuid, System.currentTimeMillis());
}
public void unlock(UUID uuid) {
lockedPlayers.remove(uuid);
}
public boolean isLocked(UUID uuid) {
return lockedPlayers.contains(uuid);
}
public long getLastLockTime(UUID uuid) {
return storage.getLastLockTime(uuid);
}
public AuthStorage getStorage() {
return storage;
}
}

View File

@ -0,0 +1,120 @@
package com.adzel.addyauthreborn.auth;
import com.adzel.addyauthreborn.AddyAuthReborn;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.UUID;
public class AuthStorage {
private final AddyAuthReborn plugin = AddyAuthReborn.getInstance();
public AuthStorage() {
try (Connection conn = plugin.getDataSource().getConnection()) {
PreparedStatement ps = conn.prepareStatement(
"CREATE TABLE IF NOT EXISTS addyauth_users (" +
"uuid VARCHAR(36) PRIMARY KEY," +
"password_hash TEXT NOT NULL," +
"last_locked_at BIGINT DEFAULT 0)"
);
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
plugin.getLogger().severe("Failed to create users table: " + e.getMessage());
}
}
public boolean isRegistered(UUID uuid) {
try (Connection conn = plugin.getDataSource().getConnection()) {
PreparedStatement ps = conn.prepareStatement(
"SELECT uuid FROM addyauth_users WHERE uuid = ?"
);
ps.setString(1, uuid.toString());
ResultSet rs = ps.executeQuery();
boolean exists = rs.next();
rs.close();
ps.close();
return exists;
} catch (SQLException e) {
plugin.getLogger().warning("Failed to check registration: " + e.getMessage());
return false;
}
}
public boolean savePassword(UUID uuid, String hashedPassword) {
try (Connection conn = plugin.getDataSource().getConnection()) {
PreparedStatement ps = conn.prepareStatement(
"INSERT INTO addyauth_users (uuid, password_hash) VALUES (?, ?)"
);
ps.setString(1, uuid.toString());
ps.setString(2, hashedPassword);
ps.executeUpdate();
ps.close();
return true;
} catch (SQLException e) {
plugin.getLogger().warning("Failed to save password: " + e.getMessage());
return false;
}
}
public String getPasswordHash(UUID uuid) {
try (Connection conn = plugin.getDataSource().getConnection()) {
PreparedStatement ps = conn.prepareStatement(
"SELECT password_hash FROM addyauth_users WHERE uuid = ?"
);
ps.setString(1, uuid.toString());
ResultSet rs = ps.executeQuery();
if (rs.next()) {
String hash = rs.getString("password_hash");
rs.close();
ps.close();
return hash;
}
rs.close();
ps.close();
} catch (SQLException e) {
plugin.getLogger().warning("Failed to retrieve password hash: " + e.getMessage());
}
return null;
}
public boolean setLastLockTime(UUID uuid, long timestamp) {
try (Connection conn = plugin.getDataSource().getConnection()) {
PreparedStatement ps = conn.prepareStatement(
"UPDATE addyauth_users SET last_locked_at = ? WHERE uuid = ?"
);
ps.setLong(1, timestamp);
ps.setString(2, uuid.toString());
int updated = ps.executeUpdate();
ps.close();
return updated > 0;
} catch (SQLException e) {
plugin.getLogger().warning("Failed to update last lock time: " + e.getMessage());
return false;
}
}
public long getLastLockTime(UUID uuid) {
try (Connection conn = plugin.getDataSource().getConnection()) {
PreparedStatement ps = conn.prepareStatement(
"SELECT last_locked_at FROM addyauth_users WHERE uuid = ?"
);
ps.setString(1, uuid.toString());
ResultSet rs = ps.executeQuery();
if (rs.next()) {
long timestamp = rs.getLong("last_locked_at");
rs.close();
ps.close();
return timestamp;
}
rs.close();
ps.close();
} catch (SQLException e) {
plugin.getLogger().warning("Failed to fetch last lock time: " + e.getMessage());
}
return 0;
}
}

View File

@ -0,0 +1,46 @@
package com.adzel.addyauthreborn.commands;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import com.adzel.addyauthreborn.AddyAuthReborn;
import com.adzel.addyauthreborn.utils.MessageUtil;
public class AddyAuthCommand implements CommandExecutor {
private final AddyAuthReborn plugin;
public AddyAuthCommand(AddyAuthReborn plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("This command can only be run by a player.");
return true;
}
if (!player.hasPermission("addyauth.admin")) {
MessageUtil.send(player, "&cYou do not have permission to use this command.");
return true;
}
if (args.length == 0) {
MessageUtil.send(player, "&eUsage: /addyauth reload");
return true;
}
if (args[0].equalsIgnoreCase("reload")) {
plugin.reloadConfig();
MessageUtil.send(player, "&aAddyAuth Reborn config reloaded.");
return true;
}
MessageUtil.send(player, "&cUnknown subcommand.");
return true;
}
}

View File

@ -0,0 +1,34 @@
package com.adzel.addyauthreborn.commands;
import com.adzel.addyauthreborn.AddyAuthReborn;
import com.adzel.addyauthreborn.utils.MessageUtil;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class LockCommand implements CommandExecutor {
private final AddyAuthReborn plugin;
public LockCommand(AddyAuthReborn plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("Only players can use this command.");
return true;
}
if (!player.hasPermission("addyauth.lock")) {
MessageUtil.send(player, "&cYou do not have permission to lock yourself.");
return true;
}
plugin.getAuthManager().lock(player.getUniqueId());
MessageUtil.send(player, "&eYou are now locked. Use &f/login <password> &eto unlock.");
return true;
}
}

View File

@ -0,0 +1,51 @@
package com.adzel.addyauthreborn.commands;
import com.adzel.addyauthreborn.AddyAuthReborn;
import com.adzel.addyauthreborn.utils.MessageUtil;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class LoginCommand implements CommandExecutor {
private final AddyAuthReborn plugin;
public LoginCommand(AddyAuthReborn plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("Only players can use this command.");
return true;
}
if (!player.hasPermission("addyauth.login")) {
MessageUtil.send(player, "&cYou do not have permission to login.");
return true;
}
if (args.length != 1) {
MessageUtil.send(player, "&cUsage: /login <password>");
return true;
}
if (!plugin.getAuthManager().isRegistered(player.getUniqueId())) {
MessageUtil.send(player, "&eYou are not registered. Use &f/register <password>&e first.");
return true;
}
String password = args[0];
boolean valid = plugin.getAuthManager().verifyPassword(player.getUniqueId(), password);
if (!valid) {
MessageUtil.send(player, "&cIncorrect password.");
return true;
}
MessageUtil.send(player, "&aYou are now logged in and unlocked.");
return true;
}
}

View File

@ -0,0 +1,58 @@
package com.adzel.addyauthreborn.commands;
import com.adzel.addyauthreborn.AddyAuthReborn;
import com.adzel.addyauthreborn.utils.MessageUtil;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class RegisterCommand implements CommandExecutor {
private final AddyAuthReborn plugin;
public RegisterCommand(AddyAuthReborn plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("Only players can use this command.");
return true;
}
if (!player.hasPermission("addyauth.register")) {
MessageUtil.send(player, "&cYou do not have permission to register.");
return true;
}
if (args.length != 1) {
MessageUtil.send(player, "&cUsage: /register <password>");
return true;
}
String password = args[0];
int minLength = plugin.getMinPasswordLength();
int maxLength = plugin.getMaxPasswordLength();
if (password.length() < minLength || password.length() > maxLength) {
MessageUtil.send(player, "&cPassword must be between " + minLength + " and " + maxLength + " characters.");
return true;
}
if (plugin.getAuthManager().isRegistered(player.getUniqueId())) {
MessageUtil.send(player, "&eYou are already registered.");
return true;
}
boolean success = plugin.getAuthManager().registerPassword(player.getUniqueId(), password);
if (success) {
MessageUtil.send(player, "&aYour password has been registered successfully.");
} else {
MessageUtil.send(player, "&cSomething went wrong. Please try again.");
}
return true;
}
}

View File

@ -0,0 +1,31 @@
package com.adzel.addyauthreborn.listeners;
import com.adzel.addyauthreborn.AddyAuthReborn;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
public class AdminJoinListener implements Listener {
private final AddyAuthReborn plugin;
public AdminJoinListener(AddyAuthReborn plugin) {
this.plugin = plugin;
}
@EventHandler
public void onAdminJoin(PlayerJoinEvent event) {
if (!plugin.isUpdateAvailable()) return;
if (event.getPlayer().hasPermission("addyauth.admin")) {
Bukkit.getScheduler().runTaskLater(plugin, () -> {
event.getPlayer().sendMessage(ChatColor.YELLOW + "[AddyAuth Reborn] " +
ChatColor.RED + "A new version is available!");
event.getPlayer().sendMessage(ChatColor.GRAY + "Check: " +
ChatColor.BLUE + "https://git.adzeldevelops.site/Adzel/AddyAuth-Reborn/releases/latest");
}, 40L); // delay a bit so it doesn't clash with login spam
}
}
}

View File

@ -0,0 +1,32 @@
package com.adzel.addyauthreborn.listeners;
import com.adzel.addyauthreborn.AddyAuthReborn;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.UUID;
public class ConnectionListener implements Listener {
private final AddyAuthReborn plugin;
public ConnectionListener(AddyAuthReborn plugin) {
this.plugin = plugin;
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
UUID uuid = event.getPlayer().getUniqueId();
plugin.getOfflineLockManager().evaluateLogin(uuid);
plugin.getAFKMonitor().updateActivity(uuid); // start tracking afk immediately
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
UUID uuid = event.getPlayer().getUniqueId();
plugin.getOfflineLockManager().recordLogout(uuid);
plugin.getAFKMonitor().removePlayer(uuid); // clean up
}
}

View File

@ -0,0 +1,90 @@
package com.adzel.addyauthreborn.listeners;
import com.adzel.addyauthreborn.AddyAuthReborn;
import com.adzel.addyauthreborn.utils.MessageUtil;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.*;
public class LockListeners implements Listener {
private final AddyAuthReborn plugin;
public LockListeners(AddyAuthReborn plugin) {
this.plugin = plugin;
}
private boolean isLocked(Player player) {
return plugin.getAuthManager().isLocked(player.getUniqueId());
}
private void updateIfNotLocked(Player player) {
if (!isLocked(player)) {
plugin.getAFKMonitor().updateActivity(player.getUniqueId());
}
}
@EventHandler
public void onMove(PlayerMoveEvent event) {
Player player = event.getPlayer();
if (isLocked(player) && event.getFrom().distanceSquared(event.getTo()) > 0) {
event.setCancelled(true);
} else if (event.getFrom().distanceSquared(event.getTo()) > 1) {
updateIfNotLocked(player);
}
}
@EventHandler
public void onChat(AsyncPlayerChatEvent event) {
Player player = event.getPlayer();
if (isLocked(player)) {
event.setCancelled(true);
MessageUtil.send(player, "&cYou cannot chat while locked.");
} else {
updateIfNotLocked(player);
}
}
@EventHandler
public void onClick(InventoryClickEvent event) {
if (event.getWhoClicked() instanceof Player player) {
if (isLocked(player)) {
event.setCancelled(true);
} else {
updateIfNotLocked(player);
}
}
}
@EventHandler
public void onDrop(PlayerDropItemEvent event) {
Player player = event.getPlayer();
if (isLocked(player)) {
event.setCancelled(true);
} else {
updateIfNotLocked(player);
}
}
@EventHandler
public void onPickup(PlayerAttemptPickupItemEvent event) {
Player player = event.getPlayer();
if (isLocked(player)) {
event.setCancelled(true);
} else {
updateIfNotLocked(player);
}
}
@EventHandler
public void onInteract(PlayerInteractEvent event) {
Player player = event.getPlayer();
if (isLocked(player)) {
event.setCancelled(true);
} else {
updateIfNotLocked(player);
}
}
}

View File

@ -0,0 +1,73 @@
package com.adzel.addyauthreborn.lock;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import com.adzel.addyauthreborn.AddyAuthReborn;
import com.adzel.addyauthreborn.utils.MessageUtil;
public class AFKMonitor {
private final AddyAuthReborn plugin;
private final Map<UUID, Long> lastActivityMap = new ConcurrentHashMap<>();
private final long timeoutMillis;
private final boolean enabled;
private BukkitRunnable monitorTask;
public AFKMonitor(AddyAuthReborn plugin) {
this.plugin = plugin;
this.enabled = plugin.getConfig().getBoolean("Time Based Settings.AFK Lock.Enabled", true);
this.timeoutMillis = plugin.getConfig().getLong("Time Based Settings.AFK Lock.Auto Lock", 30) * 60 * 1000L;
if (enabled) {
startMonitor();
}
}
public void updateActivity(UUID uuid) {
lastActivityMap.put(uuid, System.currentTimeMillis());
}
public void remove(UUID uuid) {
lastActivityMap.remove(uuid);
}
public void removePlayer(UUID uuid) {
lastActivityMap.remove(uuid);
}
private void startMonitor() {
monitorTask = new BukkitRunnable() {
@Override
public void run() {
long now = System.currentTimeMillis();
for (Player player : Bukkit.getOnlinePlayers()) {
UUID uuid = player.getUniqueId();
if (!plugin.getAuthManager().isRegistered(uuid)) continue;
if (plugin.getAuthManager().isLocked(uuid)) continue;
long lastActive = lastActivityMap.getOrDefault(uuid, now);
if ((now - lastActive) >= timeoutMillis) {
plugin.getAuthManager().lock(uuid); // ✅ Stores timestamp
MessageUtil.send(player, "&cYou have been locked due to AFK timeout.");
}
}
}
};
monitorTask.runTaskTimer(plugin, 20L * 60, 20L * 60); // every 60 seconds
}
public void shutdown() {
if (monitorTask != null) {
monitorTask.cancel();
}
lastActivityMap.clear();
}
}

View File

@ -0,0 +1,40 @@
package com.adzel.addyauthreborn.lock;
import com.adzel.addyauthreborn.AddyAuthReborn;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class OfflineLockManager {
private final AddyAuthReborn plugin;
private final Map<UUID, Long> logoutTimestamps = new ConcurrentHashMap<>();
private final long offlineThresholdMillis;
public OfflineLockManager(AddyAuthReborn plugin) {
this.plugin = plugin;
long minutes = plugin.getConfig().getLong("Time Based Settings.Offline Lock.Auto Lock", 120);
this.offlineThresholdMillis = minutes * 60_000;
}
public void recordLogout(UUID uuid) {
logoutTimestamps.put(uuid, System.currentTimeMillis());
}
public void evaluateLogin(UUID uuid) {
if (!plugin.getConfig().getBoolean("Time Based Settings.Offline Lock.Enabled", true)) return;
Long logoutTime = logoutTimestamps.get(uuid);
if (logoutTime == null) return;
long elapsed = System.currentTimeMillis() - logoutTime;
if (elapsed >= offlineThresholdMillis) {
plugin.getAuthManager().lock(uuid); // ✅ Stores timestamp
}
}
public void forget(UUID uuid) {
logoutTimestamps.remove(uuid);
}
}

View File

@ -0,0 +1,67 @@
package com.adzel.addyauthreborn.updates;
import com.adzel.addyauthreborn.AddyAuthReborn;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.function.Consumer;
public class UpdateChecker {
private final AddyAuthReborn plugin;
private final String apiUrl;
public UpdateChecker(AddyAuthReborn plugin, String apiUrl) {
this.plugin = plugin;
this.apiUrl = apiUrl;
}
public void getLatestVersion(Consumer<String> callback) {
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
try {
// Gitea's latest release endpoint returns a JSON object
URL url = new URL(apiUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("Accept", "application/json");
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
if (connection.getResponseCode() != 200) {
plugin.getLogger().warning("Update check failed: Invalid response from Gitea (" + connection.getResponseCode() + ")");
return;
}
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder json = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
json.append(line);
}
reader.close();
String latest = parseTagName(json.toString());
if (latest != null) {
callback.accept(latest);
} else {
plugin.getLogger().warning("Update check failed: Could not parse latest tag.");
}
} catch (Exception e) {
plugin.getLogger().warning("Update check failed: " + e.getMessage());
}
});
}
private String parseTagName(String json) {
// crude but fast — this works with Giteas /releases/latest API
String key = "\"tag_name\":\"";
int index = json.indexOf(key);
if (index == -1) return null;
int start = index + key.length();
int end = json.indexOf("\"", start);
return end == -1 ? null : json.substring(start, end);
}
}

View File

@ -0,0 +1,78 @@
package com.adzel.addyauthreborn.utils;
import com.adzel.addyauthreborn.AddyAuthReborn;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class DatabaseUtil {
private static HikariDataSource dataSource;
public static boolean initDatabase() {
AddyAuthReborn plugin = AddyAuthReborn.getInstance();
String type = plugin.getConfig().getString("Database.Type", "sqlite").toLowerCase();
HikariConfig config = new HikariConfig();
try {
switch (type) {
case "mysql":
case "mariadb":
config.setJdbcUrl("jdbc:mariadb://" +
plugin.getConfig().getString("Database.Host", "localhost") + ":" +
plugin.getConfig().getInt("Database.Port", 3306) + "/" +
plugin.getConfig().getString("Database.Name", "addyauth"));
config.setUsername(plugin.getConfig().getString("Database.Username", "root"));
config.setPassword(plugin.getConfig().getString("Database.Password", ""));
config.setDriverClassName("org.mariadb.jdbc.Driver");
break;
case "sqlite":
default:
config.setJdbcUrl("jdbc:sqlite:" + plugin.getDataFolder() + "/addyauth.db");
config.setDriverClassName("org.sqlite.JDBC");
break;
}
config.setMaximumPoolSize(5);
config.setPoolName("AddyAuthPool");
dataSource = new HikariDataSource(config);
try (Connection conn = dataSource.getConnection()) {
createTables(conn);
}
return true;
} catch (Exception e) {
plugin.getLogger().severe("Database initialization failed: " + e.getMessage());
return false;
}
}
private static void createTables(Connection conn) throws SQLException {
try (Statement stmt = conn.createStatement()) {
stmt.executeUpdate("""
CREATE TABLE IF NOT EXISTS addyauth_users (
uuid VARCHAR(36) PRIMARY KEY,
password_hash TEXT NOT NULL,
last_locked_at BIGINT DEFAULT 0
)
""");
}
}
public static HikariDataSource getDataSource() {
return dataSource;
}
public static void shutdown() {
if (dataSource != null && !dataSource.isClosed()) {
dataSource.close();
}
}
}

View File

@ -0,0 +1,25 @@
package com.adzel.addyauthreborn.utils;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.entity.Player;
public class MessageUtil {
private static final MiniMessage mini = MiniMessage.miniMessage();
private static final LegacyComponentSerializer legacy = LegacyComponentSerializer.builder()
.character('&')
.hexColors()
.build();
public static void send(Player player, String message) {
// Convert &x style to MiniMessage
Component component = mini.deserialize(legacy.serialize(legacy.deserialize(message)));
player.sendMessage(component);
}
public static Component parse(String message) {
return mini.deserialize(legacy.serialize(legacy.deserialize(message)));
}
}

View File

@ -0,0 +1,53 @@
name: AddyAuthReborn
version: 0.1-pre
main: com.adzel.addyauthreborn.AddyAuthReborn
api-version: 1.20
author: AdzelFirestar
description: >
A secure authentication system for staff accounts, requiring a password before access to movement, interaction, and chat.
commands:
register:
description: Register your staff password for the first time.
usage: /register <password>
permission: addyauth.register
login:
description: Login using your staff password to unlock yourself.
usage: /login <password>
permission: addyauth.login
lock:
description: Lock your own account until you login again.
usage: /lock
permission: addyauth.lock
addyauth:
description: Admin command for AddyAuth (reload, future diagnostics, etc.)
usage: /addyauth reload
permission: addyauth.admin
permissions:
addyauth.*:
description: Gives access to all AddyAuth commands.
children:
addyauth.register: true
addyauth.login: true
addyauth.lock: true
addyauth.admin: true
addyauth.register:
description: Allows staff to register their password.
default: op
addyauth.login:
description: Allows staff to login to unlock themselves.
default: op
addyauth.lock:
description: Allows staff to lock their account.
default: op
addyauth.admin:
description: Grants access to admin commands and update notifications.
default: op

53
target/classes/plugin.yml Normal file
View File

@ -0,0 +1,53 @@
name: AddyAuthReborn
version: 0.1-pre
main: com.adzel.addyauthreborn.AddyAuthReborn
api-version: 1.20
author: AdzelFirestar
description: >
A secure authentication system for staff accounts, requiring a password before access to movement, interaction, and chat.
commands:
register:
description: Register your staff password for the first time.
usage: /register <password>
permission: addyauth.register
login:
description: Login using your staff password to unlock yourself.
usage: /login <password>
permission: addyauth.login
lock:
description: Lock your own account until you login again.
usage: /lock
permission: addyauth.lock
addyauth:
description: Admin command for AddyAuth (reload, future diagnostics, etc.)
usage: /addyauth reload
permission: addyauth.admin
permissions:
addyauth.*:
description: Gives access to all AddyAuth commands.
children:
addyauth.register: true
addyauth.login: true
addyauth.lock: true
addyauth.admin: true
addyauth.register:
description: Allows staff to register their password.
default: op
addyauth.login:
description: Allows staff to login to unlock themselves.
default: op
addyauth.lock:
description: Allows staff to lock their account.
default: op
addyauth.admin:
description: Grants access to admin commands and update notifications.
default: op

View File

@ -0,0 +1,3 @@
artifactId=AddyAuthReborn
groupId=com.adzel.addyauthreborn
version=0.1-pre

View File

@ -0,0 +1,16 @@
com\adzel\addyauthreborn\lock\AFKMonitor$1.class
com\adzel\addyauthreborn\commands\LoginCommand.class
com\adzel\addyauthreborn\listeners\ConnectionListener.class
com\adzel\addyauthreborn\utils\DatabaseUtil.class
com\adzel\addyauthreborn\auth\AuthStorage.class
com\adzel\addyauthreborn\commands\LockCommand.class
com\adzel\addyauthreborn\lock\AFKMonitor.class
com\adzel\addyauthreborn\commands\AddyAuthCommand.class
com\adzel\addyauthreborn\commands\RegisterCommand.class
com\adzel\addyauthreborn\auth\AuthManager.class
com\adzel\addyauthreborn\listeners\LockListeners.class
com\adzel\addyauthreborn\AddyAuthReborn.class
com\adzel\addyauthreborn\listeners\AdminJoinListener.class
com\adzel\addyauthreborn\utils\MessageUtil.class
com\adzel\addyauthreborn\updates\UpdateChecker.class
com\adzel\addyauthreborn\lock\OfflineLockManager.class

View File

@ -0,0 +1,15 @@
C:\Users\theon\OneDrive\Desktop\AddyAuth Reborn\src\main\java\com\adzel\addyauthreborn\AddyAuthReborn.java
C:\Users\theon\OneDrive\Desktop\AddyAuth Reborn\src\main\java\com\adzel\addyauthreborn\listeners\AdminJoinListener.java
C:\Users\theon\OneDrive\Desktop\AddyAuth Reborn\src\main\java\com\adzel\addyauthreborn\listeners\LockListeners.java
C:\Users\theon\OneDrive\Desktop\AddyAuth Reborn\src\main\java\com\adzel\addyauthreborn\listeners\ConnectionListener.java
C:\Users\theon\OneDrive\Desktop\AddyAuth Reborn\src\main\java\com\adzel\addyauthreborn\commands\LockCommand.java
C:\Users\theon\OneDrive\Desktop\AddyAuth Reborn\src\main\java\com\adzel\addyauthreborn\lock\OfflineLockManager.java
C:\Users\theon\OneDrive\Desktop\AddyAuth Reborn\src\main\java\com\adzel\addyauthreborn\utils\MessageUtil.java
C:\Users\theon\OneDrive\Desktop\AddyAuth Reborn\src\main\java\com\adzel\addyauthreborn\commands\LoginCommand.java
C:\Users\theon\OneDrive\Desktop\AddyAuth Reborn\src\main\java\com\adzel\addyauthreborn\updates\UpdateChecker.java
C:\Users\theon\OneDrive\Desktop\AddyAuth Reborn\src\main\java\com\adzel\addyauthreborn\commands\AddyAuthCommand.java
C:\Users\theon\OneDrive\Desktop\AddyAuth Reborn\src\main\java\com\adzel\addyauthreborn\utils\DatabaseUtil.java
C:\Users\theon\OneDrive\Desktop\AddyAuth Reborn\src\main\java\com\adzel\addyauthreborn\auth\AuthStorage.java
C:\Users\theon\OneDrive\Desktop\AddyAuth Reborn\src\main\java\com\adzel\addyauthreborn\auth\AuthManager.java
C:\Users\theon\OneDrive\Desktop\AddyAuth Reborn\src\main\java\com\adzel\addyauthreborn\commands\RegisterCommand.java
C:\Users\theon\OneDrive\Desktop\AddyAuth Reborn\src\main\java\com\adzel\addyauthreborn\lock\AFKMonitor.java