parent
799833b685
commit
29df176ff7
9 changed files with 379 additions and 5 deletions
|
@ -42,7 +42,11 @@ public record TweaksConfig(
|
||||||
float hardcoreChance,
|
float hardcoreChance,
|
||||||
|
|
||||||
boolean sleepEnabled,
|
boolean sleepEnabled,
|
||||||
boolean sleepInstant
|
boolean sleepInstant,
|
||||||
|
|
||||||
|
boolean authEnabled,
|
||||||
|
boolean authForce,
|
||||||
|
String authDomain
|
||||||
) {
|
) {
|
||||||
public static final int CONFIG_VERSION = 1;
|
public static final int CONFIG_VERSION = 1;
|
||||||
private static TweaksConfig config;
|
private static TweaksConfig config;
|
||||||
|
@ -99,6 +103,10 @@ public record TweaksConfig(
|
||||||
boolean sleepEnabled = config.getBoolean("sleep.enabled");
|
boolean sleepEnabled = config.getBoolean("sleep.enabled");
|
||||||
boolean sleepInstant = config.getBoolean("sleep.instant");
|
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(
|
TweaksConfig.config = new TweaksConfig(
|
||||||
worldborderExpand, worldborderHide,
|
worldborderExpand, worldborderHide,
|
||||||
brandEnabled, brandText, brandShowPing, brandShowMspt,
|
brandEnabled, brandText, brandShowPing, brandShowMspt,
|
||||||
|
@ -109,7 +117,8 @@ public record TweaksConfig(
|
||||||
pomodoroEnabled, pomodoroForce,
|
pomodoroEnabled, pomodoroForce,
|
||||||
updaterEnabled,
|
updaterEnabled,
|
||||||
hardcoreEnabled, hardcoreChance,
|
hardcoreEnabled, hardcoreChance,
|
||||||
sleepEnabled, sleepInstant
|
sleepEnabled, sleepInstant,
|
||||||
|
authEnabled, authForce, authHostname
|
||||||
);
|
);
|
||||||
|
|
||||||
return TweaksConfig.config;
|
return TweaksConfig.config;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
package eu.m724.tweaks;
|
package eu.m724.tweaks;
|
||||||
|
|
||||||
|
import eu.m724.tweaks.auth.AuthManager;
|
||||||
import eu.m724.tweaks.chat.ChatCommands;
|
import eu.m724.tweaks.chat.ChatCommands;
|
||||||
import eu.m724.tweaks.chat.ChatManager;
|
import eu.m724.tweaks.chat.ChatManager;
|
||||||
import eu.m724.tweaks.door.DoorManager;
|
import eu.m724.tweaks.door.DoorManager;
|
||||||
|
@ -94,6 +95,10 @@ public class TweaksPlugin extends JavaPlugin {
|
||||||
new SleepManager().init(this);
|
new SleepManager().init(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.authEnabled()) {
|
||||||
|
new AuthManager(this).init(getCommand("tauth"));
|
||||||
|
}
|
||||||
|
|
||||||
getLogger().info("Took %.3f milliseconds".formatted((System.nanoTime() - start) / 1000000.0));
|
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 instant: how much % of players to skip the night
|
||||||
# If not: how much % make skipping full speed
|
# 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!
|
# Finally, thank you for downloading Tweaks724, I hope you enjoy!
|
||||||
|
|
||||||
# Don't modify unless told to
|
# Don't modify unless told to
|
||||||
|
|
|
@ -27,14 +27,17 @@ commands:
|
||||||
description: See available plugin updates
|
description: See available plugin updates
|
||||||
permission: tweaks724.updates
|
permission: tweaks724.updates
|
||||||
aliases: [pluginupdates]
|
aliases: [pluginupdates]
|
||||||
|
tauth:
|
||||||
|
description: Authentication management
|
||||||
|
permission: tweaks724.tauth
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
tweaks724.chatmanage:
|
tweaks724.chatmanage:
|
||||||
default: true
|
default: true
|
||||||
tweaks724.dkick:
|
|
||||||
default: op
|
|
||||||
tweaks724.pomodoro:
|
tweaks724.pomodoro:
|
||||||
default: true
|
default: true
|
||||||
tweaks724.updates:
|
tweaks724.updates:
|
||||||
default: op
|
default: op
|
||||||
|
tweaks724.tauth:
|
||||||
|
default: op
|
||||||
|
|
||||||
|
|
|
@ -21,4 +21,9 @@ chatNoSuchRoom = No room named %s
|
||||||
chatAlreadyHere = You're already in this room
|
chatAlreadyHere = You're already in this room
|
||||||
# Room name is added at end
|
# Room name is added at end
|
||||||
chatJoined = Joined chat room:
|
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