parent
799833b685
commit
29df176ff7
9 changed files with 379 additions and 5 deletions
|
@ -42,7 +42,11 @@ public record TweaksConfig(
|
|||
float hardcoreChance,
|
||||
|
||||
boolean sleepEnabled,
|
||||
boolean sleepInstant
|
||||
boolean sleepInstant,
|
||||
|
||||
boolean authEnabled,
|
||||
boolean authForce,
|
||||
String authDomain
|
||||
) {
|
||||
public static final int CONFIG_VERSION = 1;
|
||||
private static TweaksConfig config;
|
||||
|
@ -99,6 +103,10 @@ public record TweaksConfig(
|
|||
boolean sleepEnabled = config.getBoolean("sleep.enabled");
|
||||
boolean sleepInstant = config.getBoolean("sleep.instant");
|
||||
|
||||
boolean authEnabled = config.getBoolean("auth.enabled");
|
||||
boolean authForce = config.getBoolean("auth.force");
|
||||
String authHostname = config.getString("auth.domain");
|
||||
|
||||
TweaksConfig.config = new TweaksConfig(
|
||||
worldborderExpand, worldborderHide,
|
||||
brandEnabled, brandText, brandShowPing, brandShowMspt,
|
||||
|
@ -109,7 +117,8 @@ public record TweaksConfig(
|
|||
pomodoroEnabled, pomodoroForce,
|
||||
updaterEnabled,
|
||||
hardcoreEnabled, hardcoreChance,
|
||||
sleepEnabled, sleepInstant
|
||||
sleepEnabled, sleepInstant,
|
||||
authEnabled, authForce, authHostname
|
||||
);
|
||||
|
||||
return TweaksConfig.config;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
package eu.m724.tweaks;
|
||||
|
||||
import eu.m724.tweaks.auth.AuthManager;
|
||||
import eu.m724.tweaks.chat.ChatCommands;
|
||||
import eu.m724.tweaks.chat.ChatManager;
|
||||
import eu.m724.tweaks.door.DoorManager;
|
||||
|
@ -94,6 +95,10 @@ public class TweaksPlugin extends JavaPlugin {
|
|||
new SleepManager().init(this);
|
||||
}
|
||||
|
||||
if (config.authEnabled()) {
|
||||
new AuthManager(this).init(getCommand("tauth"));
|
||||
}
|
||||
|
||||
getLogger().info("Took %.3f milliseconds".formatted((System.nanoTime() - start) / 1000000.0));
|
||||
}
|
||||
|
||||
|
|
93
src/main/java/eu/m724/tweaks/auth/AuthCommands.java
Normal file
93
src/main/java/eu/m724/tweaks/auth/AuthCommands.java
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
|
||||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.auth;
|
||||
|
||||
import eu.m724.tweaks.TweaksConfig;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
import net.md_5.bungee.api.chat.hover.content.Text;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AuthCommands implements CommandExecutor {
|
||||
private final AuthStorage authStorage;
|
||||
|
||||
AuthCommands(AuthStorage authStorage) {
|
||||
this.authStorage = authStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (args.length == 0) {
|
||||
sender.sendMessage("<key> | new | delete");
|
||||
return true;
|
||||
}
|
||||
|
||||
String action = args[0];
|
||||
|
||||
if (action.equals("new")) {
|
||||
String key = authStorage.generateKey();
|
||||
authStorage.create(key);
|
||||
String hostname = key + "." + TweaksConfig.getConfig().authDomain();
|
||||
|
||||
if (sender instanceof Player) {
|
||||
BaseComponent component = new ComponentBuilder("Generated a new key. Click to copy.")
|
||||
.underlined(true)
|
||||
.color(ChatColor.GRAY)
|
||||
.event(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, hostname))
|
||||
.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("Click to copy")))
|
||||
.build();
|
||||
sender.spigot().sendMessage(component);
|
||||
} else {
|
||||
sender.sendMessage("Generated a new key: " + hostname);
|
||||
}
|
||||
} else if (action.equals("delete")) {
|
||||
if (args.length == 1) {
|
||||
sender.sendMessage("You need to pass the key you want to delete as an argument");
|
||||
return true;
|
||||
}
|
||||
|
||||
UUID user = authStorage.delete(args[1].split("\\.")[0]);
|
||||
|
||||
if (user != null) {
|
||||
if (user.getLeastSignificantBits() == 0 && user.getMostSignificantBits() == 0) {
|
||||
sender.sendMessage("Key deleted. It wasn't assigned to any player.");
|
||||
} else {
|
||||
sender.sendMessage("Key deleted. It was assigned to " + Bukkit.getOfflinePlayer(user).getName());
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("There's no such key.");
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
UUID user = authStorage.getUserOfKey(action.split("\\.")[0]);
|
||||
|
||||
if (user == null) {
|
||||
sender.sendMessage("Key has no owner, meaning anybody who uses it gets to keep it");
|
||||
} else {
|
||||
OfflinePlayer player = Bukkit.getOfflinePlayer(user);
|
||||
sender.sendMessage("Key is assigned to %s %s".formatted(player.getName(), user));
|
||||
}
|
||||
} catch (FileNotFoundException | AuthStorage.InvalidKeyException e) {
|
||||
sender.sendMessage("No such key. Enter a valid key. Or you meant 'new' or 'delete'?");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
54
src/main/java/eu/m724/tweaks/auth/AuthListener.java
Normal file
54
src/main/java/eu/m724/tweaks/auth/AuthListener.java
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
|
||||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.auth;
|
||||
|
||||
import eu.m724.tweaks.Language;
|
||||
import eu.m724.tweaks.TweaksConfig;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerLoginEvent;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
public class AuthListener implements Listener {
|
||||
private final AuthStorage authStorage;
|
||||
private final boolean force = TweaksConfig.getConfig().authForce();
|
||||
|
||||
public AuthListener(AuthStorage authStorage) {
|
||||
this.authStorage = authStorage;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerLogin(PlayerLoginEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
|
||||
String key = event.getHostname().split("\\.")[0];
|
||||
|
||||
boolean allowed = false;
|
||||
String expected = authStorage.getKeyOfUser(player.getUniqueId());
|
||||
|
||||
if (key.equals(expected)) {
|
||||
allowed = true; // key matches player
|
||||
} else if (expected == null) { // player doesn't have key
|
||||
try {
|
||||
authStorage.assignOwner(key, player.getUniqueId());
|
||||
allowed = true; // key just assigned
|
||||
} catch (FileNotFoundException | AuthStorage.AlreadyClaimedException | AuthStorage.InvalidKeyException e) {
|
||||
allowed = !force; // If forced all players must have a key
|
||||
}
|
||||
}
|
||||
|
||||
if (!allowed) {
|
||||
if (expected == null) { // if player is new
|
||||
event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, Language.getString("authKickUnregistered"));
|
||||
} else {
|
||||
event.disallow(PlayerLoginEvent.Result.KICK_OTHER, Language.getString("authKickWrongKey"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
src/main/java/eu/m724/tweaks/auth/AuthManager.java
Normal file
25
src/main/java/eu/m724/tweaks/auth/AuthManager.java
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
|
||||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.auth;
|
||||
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
public class AuthManager {
|
||||
private final AuthStorage authStorage;
|
||||
private final Plugin plugin;
|
||||
|
||||
public AuthManager(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.authStorage = new AuthStorage(plugin);
|
||||
}
|
||||
|
||||
public void init(PluginCommand command) {
|
||||
plugin.getServer().getPluginManager().registerEvents(new AuthListener(authStorage), plugin);
|
||||
command.setExecutor(new AuthCommands(authStorage));
|
||||
}
|
||||
}
|
170
src/main/java/eu/m724/tweaks/auth/AuthStorage.java
Normal file
170
src/main/java/eu/m724/tweaks/auth/AuthStorage.java
Normal file
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Minecon724
|
||||
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
|
||||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.tweaks.auth;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AuthStorage {
|
||||
private final File playersDirectory;
|
||||
private final File keysDirectory;
|
||||
|
||||
AuthStorage(Plugin plugin) {
|
||||
File directory = new File(plugin.getDataFolder(), "auth storage");
|
||||
this.playersDirectory = new File(directory, "players");
|
||||
this.keysDirectory = new File(directory, "keys");
|
||||
|
||||
directory.mkdir();
|
||||
keysDirectory.mkdir();
|
||||
playersDirectory.mkdir();
|
||||
}
|
||||
|
||||
private boolean isInvalid(String key) {
|
||||
return !key.matches("^[a-zA-Z0-9]{4,100}$");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the owner of a key
|
||||
* @param key the key
|
||||
* @return the owner's UUID or null if no owner
|
||||
* @throws FileNotFoundException if no such key
|
||||
*/
|
||||
UUID getUserOfKey(String key) throws FileNotFoundException {
|
||||
if (isInvalid(key)) throw new InvalidKeyException();
|
||||
|
||||
File file = new File(keysDirectory, key);
|
||||
if (!file.exists()) throw new FileNotFoundException();
|
||||
|
||||
byte[] buf = new byte[16];
|
||||
|
||||
try (FileInputStream is = new FileInputStream(file)) {
|
||||
int read = is.read(buf);
|
||||
if (read < 16) return null;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // TODO
|
||||
}
|
||||
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(buf);
|
||||
long msb = byteBuffer.getLong();
|
||||
long lsb = byteBuffer.getLong();
|
||||
return new UUID(msb, lsb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the key of a user
|
||||
* @param uuid the user's UUID
|
||||
* @return the key of the user or null if no owned key
|
||||
*/
|
||||
String getKeyOfUser(UUID uuid) {
|
||||
File file = new File(playersDirectory, uuid.toString());
|
||||
if (!file.exists()) return null;
|
||||
|
||||
try (FileInputStream is = new FileInputStream(file)) {
|
||||
byte[] bytes = is.readNBytes(50);
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // TODO
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a key that has no owner
|
||||
* @param key the key to create
|
||||
*/
|
||||
void create(String key) {
|
||||
if (isInvalid(key)) throw new InvalidKeyException();
|
||||
|
||||
File file = new File(keysDirectory, key);
|
||||
try {
|
||||
file.createNewFile();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a key
|
||||
* @param key the key
|
||||
* @return the user that was using this key, UUID(0, 0) if none, null if key didn't exist
|
||||
*/
|
||||
UUID delete(String key) {
|
||||
if (isInvalid(key)) return null;
|
||||
|
||||
UUID user = null;
|
||||
|
||||
try {
|
||||
user = getUserOfKey(key);
|
||||
if (user != null) {
|
||||
new File(playersDirectory, user.toString()).delete();
|
||||
}
|
||||
} catch (FileNotFoundException e) { }
|
||||
|
||||
if (!new File(keysDirectory, key).delete())
|
||||
return null;
|
||||
|
||||
return user == null ? new UUID(0, 0) : user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns an owner to a key
|
||||
* @param key the key
|
||||
* @param uuid the owner's UUID
|
||||
* @throws FileNotFoundException if no such key
|
||||
* @throws AlreadyClaimedException if key is claimed or user owns another key
|
||||
*/
|
||||
void assignOwner(String key, UUID uuid) throws FileNotFoundException, AlreadyClaimedException {
|
||||
if (isInvalid(key)) throw new InvalidKeyException();
|
||||
|
||||
if (getUserOfKey(key) != null) throw new AlreadyClaimedException();
|
||||
if (getKeyOfUser(uuid) != null) throw new AlreadyClaimedException();
|
||||
|
||||
File file = new File(keysDirectory, key);
|
||||
if (!file.exists()) throw new FileNotFoundException();
|
||||
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(16);
|
||||
byteBuffer.putLong(uuid.getMostSignificantBits());
|
||||
byteBuffer.putLong(uuid.getLeastSignificantBits());
|
||||
|
||||
try (FileOutputStream os = new FileOutputStream(file)) {
|
||||
os.write(byteBuffer.array());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // TODO
|
||||
}
|
||||
|
||||
File file2 = new File(playersDirectory, uuid.toString());
|
||||
|
||||
try (FileOutputStream os = new FileOutputStream(file2)) {
|
||||
os.write(key.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // TODO
|
||||
}
|
||||
}
|
||||
|
||||
// TODO improve
|
||||
String generateKey() {
|
||||
char[] chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
|
||||
Random random = new Random();
|
||||
int length = random.nextInt(8, 10);
|
||||
|
||||
StringBuilder key = new StringBuilder();
|
||||
|
||||
for (int i=0; i<length; i++) {
|
||||
key.append(chars[random.nextInt(chars.length)]);
|
||||
}
|
||||
|
||||
return key.toString();
|
||||
}
|
||||
|
||||
static class InvalidKeyException extends RuntimeException {}
|
||||
static class AlreadyClaimedException extends Exception {}
|
||||
}
|
|
@ -88,6 +88,16 @@ sleep:
|
|||
# If instant: how much % of players to skip the night
|
||||
# If not: how much % make skipping full speed
|
||||
|
||||
# "Hostname" authentication
|
||||
# This makes a player need to join a unique hostname like "23sedf345.myserver.com" where "23sedf345" is the key
|
||||
auth:
|
||||
enabled: true
|
||||
# All players must use a key. So new players will need a new key
|
||||
force: false
|
||||
# The domain of the server, if it's myserver.com codes will be like 12d3123.myserver.com
|
||||
# This doesn't do anything other than showing in /tauth new
|
||||
domain: "replace.me"
|
||||
|
||||
# Finally, thank you for downloading Tweaks724, I hope you enjoy!
|
||||
|
||||
# Don't modify unless told to
|
||||
|
|
|
@ -27,14 +27,17 @@ commands:
|
|||
description: See available plugin updates
|
||||
permission: tweaks724.updates
|
||||
aliases: [pluginupdates]
|
||||
tauth:
|
||||
description: Authentication management
|
||||
permission: tweaks724.tauth
|
||||
|
||||
permissions:
|
||||
tweaks724.chatmanage:
|
||||
default: true
|
||||
tweaks724.dkick:
|
||||
default: op
|
||||
tweaks724.pomodoro:
|
||||
default: true
|
||||
tweaks724.updates:
|
||||
default: op
|
||||
tweaks724.tauth:
|
||||
default: op
|
||||
|
||||
|
|
|
@ -21,4 +21,9 @@ chatNoSuchRoom = No room named %s
|
|||
chatAlreadyHere = You're already in this room
|
||||
# Room name is added at end
|
||||
chatJoined = Joined chat room:
|
||||
chatPlayers = %d other players are here
|
||||
chatPlayers = %d other players are here
|
||||
|
||||
# Used when a player joins using the wrong key or no key
|
||||
authKickWrongKey = You're connecting to the wrong server address. You must connect to the one you're registered to.
|
||||
# If force is enabled and player is not registered. Changing this reveals you're using this plugin
|
||||
authKickUnregistered = You are not whitelisted on this server!
|
Loading…
Reference in a new issue