ADD BUILT-IN AUTHENTICATION :D:D:D

who doesn't like more proprietary file formats?????????
This commit is contained in:
ayunami2000 2022-07-08 15:59:28 -04:00
parent 5e5e54dde1
commit c704c49162
10 changed files with 262 additions and 13 deletions

View File

@ -1,11 +1,13 @@
# ayungee
lightweight bungeecord alternative for eaglercraft servers running protocolsupport
~~lightweight~~ bungeecord alternative for eaglercraft servers running protocolsupport
now contains an optional **login system**!! :D
Thanks to LAX1DUDE and md-5 for very small snippets of EaglerBungee used in this project (specifically for the server icon, skins, & entity remapping)
**TODO: built-in auth system & more bungee backwards compatibility**
**TODO: more bungee backwards compatibility, a few more kick messages/reasons, scoreboard & tab clearing, & automatic java player skins**
**if you have questions about the license, please, reach out to me. i just put the license i put on most of my projects on this, but if you have a problem with it, let me know.
**if you have questions about the license, please, reach out to me. i just put the license i put on most of my projects on this, but if you have a problem with it, **let me know**.

View File

@ -29,6 +29,11 @@
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>de.mkammerer</groupId>
<artifactId>argon2-jvm</artifactId>
<version>2.11</version>
</dependency>
</dependencies>
<build>

View File

@ -0,0 +1,118 @@
package me.ayunami2000.ayungee;
import de.mkammerer.argon2.Argon2;
import de.mkammerer.argon2.Argon2Factory;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
public class Auth {
private static class AuthData {
public String passHash;
public Set<String> ips;
public AuthData(String p, Set<String> i) {
passHash = p;
ips = i;
}
}
private static final Argon2 argon2 = Argon2Factory.create(Argon2Factory.Argon2Types.ARGON2id);
private static Map<String, AuthData> database = new HashMap<>();
public static boolean register(String username, char[] password, String ip) {
AuthData authData = database.get(username);
if (authData != null) return false;
if (isIpAtTheLimit(ip)) return false;
String hash = argon2.hash(10, 65536, 1, password);
Set<String> initIps = new HashSet<>();
initIps.add(ip);
database.put(username, new AuthData(hash, initIps));
writeDatabase();
return true;
// todo: registering & packet cancellation
}
public static boolean isRegistered(String username) {
return database.containsKey(username);
}
public static boolean changePass(String username, char[] password) {
AuthData authData = database.get(username);
authData.passHash = argon2.hash(10, 65536, 1, password);
writeDatabase();
return true;
}
public static boolean login(String username, char[] password) {
AuthData authData = database.get(username);
if (authData == null) return false;
return argon2.verify(authData.passHash, password);
}
private static boolean isIpAtTheLimit(String ip) {
if (Main.authIpLimit <= 0) return false;
Map<String, AuthData> cache = new HashMap<>(database);
int num = 0;
for (AuthData authData : cache.values()) {
if (authData.ips.contains(ip)) num++;
if (num >= Main.authIpLimit) {
cache.clear();
return true;
}
}
cache.clear();
return false;
}
// only use once, on load
public static void readDatabase() {
try {
File authFile = new File("auth.uwu");
if (!authFile.exists()) authFile.createNewFile();
Map<String, AuthData> cache = new HashMap<>();
String[] lines = new String(Files.readAllBytes(authFile.toPath())).trim().split("\n");
if (lines.length == 1 && lines[0].isEmpty()) return;
for (String line : lines) {
String[] pieces = line.split("\u0000");
cache.put(pieces[0], new AuthData(pieces[2], new HashSet<>(Arrays.asList(pieces[1].split("§")))));
}
database.clear();
database.putAll(cache);
cache.clear();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void writeDatabase() {
StringBuilder out = new StringBuilder();
Map<String, AuthData> cache = new HashMap<>(database);
for (String username : cache.keySet()) {
AuthData entry = cache.get(username);
out.append(username);
out.append("\u0000");
out.append(String.join("§", entry.ips));
out.append("\u0000");
out.append(entry.passHash);
out.append("\n");
}
cache.clear();
try {
Files.write(Paths.get("auth.uwu"), out.toString().getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -1,6 +1,5 @@
package me.ayunami2000.ayungee;
import java.io.IOException;
import java.nio.ByteBuffer;
public class ChatHandler {
@ -14,9 +13,15 @@ public class ChatHandler {
if (!message.startsWith("/")) return false;
int ind = message.indexOf(' ');
String commandBase = message.substring(1, ind != -1 ? ind : message.length()).toLowerCase();
String args = ind != -1 ? message.substring(ind + 1) : "";
String args = ind == -1 ? "" : message.substring(ind + 1);
int ind2 = args.indexOf(' ');
// todo: make it an array at this point dumbass
String firstArg = ind2 == -1 ? args : args.substring(0, ind);
String secondArg = args.substring(firstArg.length());
switch (commandBase) {
case "server":
if (!client.authed) return false;
if (args.isEmpty()) {
//usage msg
client.conn.send(new byte[] { 3, 0, 25, 0, (byte) 167, 0, 57, 0, 85, 0, 115, 0, 97, 0, 103, 0, 101, 0, 58, 0, 32, 0, 47, 0, 115, 0, 101, 0, 114, 0, 118, 0, 101, 0, 114, 0, 32, 0, 60, 0, 110, 0, 117, 0, 109, 0, 98, 0, 101, 0, 114, 0, 62 });
@ -30,14 +35,70 @@ public class ChatHandler {
}
}
break;
/*
case "register":
case "reg":
if (!Main.useAuth) return false;
if (client.authed) return false;
if (firstArg.isEmpty()) {
client.conn.send(new byte[] { 3, 0, 30, 0, (byte) 167, 0, 57, 0, 89, 0, 111, 0, 117, 0, 32, 0, 109, 0, 117, 0, 115, 0, 116, 0, 32, 0, 115, 0, 112, 0, 101, 0, 99, 0, 105, 0, 102, 0, 121, 0, 32, 0, 97, 0, 32, 0, 112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 46 });
return true;
}
if (!firstArg.equals(secondArg)) {
client.conn.send(new byte[] { 3, 0, 31, 0, (byte) 167, 0, 57, 0, 84, 0, 104, 0, 111, 0, 115, 0, 101, 0, 32, 0, 112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 115, 0, 32, 0, 100, 0, 111, 0, 32, 0, 110, 0, 111, 0, 116, 0, 32, 0, 109, 0, 97, 0, 116, 0, 99, 0, 104, 0, 33 });
return true;
}
if (Auth.register(client.username, firstArg.toCharArray(), Main.getIp(client.conn))) {
Main.printMsg("Player " + client + " registered successfully!");
client.authed = true;
client.server = -1;
} else {
client.conn.send(new byte[] { (byte) 255, 0, 38, 0, (byte) 167, 0, 57, 0, 84, 0, 104, 0, 105, 0, 115, 0, 32, 0, 117, 0, 115, 0, 101, 0, 114, 0, 110, 0, 97, 0, 109, 0, 101, 0, 32, 0, 105, 0, 115, 0, 32, 0, 97, 0, 108, 0, 114, 0, 101, 0, 97, 0, 100, 0, 121, 0, 32, 0, 114, 0, 101, 0, 103, 0, 105, 0, 115, 0, 116, 0, 101, 0, 114, 0, 101, 0, 100, 0, 33 });
client.conn.close();
}
break;
case "login":
case "l":
if (!Main.useAuth) return false;
if (client.authed) return false;
if (firstArg.isEmpty()) {
client.conn.send(new byte[] { 3, 0, 30, 0, (byte) 167, 0, 57, 0, 89, 0, 111, 0, 117, 0, 32, 0, 109, 0, 117, 0, 115, 0, 116, 0, 32, 0, 115, 0, 112, 0, 101, 0, 99, 0, 105, 0, 102, 0, 121, 0, 32, 0, 97, 0, 32, 0, 112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 46 });
return true;
}
if (Auth.login(client.username, firstArg.toCharArray())) {
Main.printMsg("Player " + client + " logged in!");
client.authed = true;
} else {
client.conn.send(new byte[] { (byte) 255, 0, 29, 0, (byte) 167, 0, 57, 0, 84, 0, 104, 0, 97, 0, 116, 0, 32, 0, 112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 32, 0, 105, 0, 115, 0, 32, 0, 105, 0, 110, 0, 99, 0, 111, 0, 114, 0, 114, 0, 101, 0, 99, 0, 116, 0, 33 });
client.conn.close();
}
break;
case "changepass":
case "changepassword":
case "changeepasswd":
case "changepwd":
case "changepw":
if (!Main.useAuth) return false;
if (!client.authed) return false;
if (firstArg.isEmpty() || secondArg.isEmpty()) {
client.conn.send(new byte[] { 3, 0, 63, 0, (byte) 167, 0, 57, 0, 80, 0, 108, 0, 101, 0, 97, 0, 115, 0, 101, 0, 32, 0, 115, 0, 112, 0, 101, 0, 99, 0, 105, 0, 102, 0, 121, 0, 32, 0, 116, 0, 104, 0, 101, 0, 32, 0, 111, 0, 108, 0, 100, 0, 32, 0, 112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 32, 0, 102, 0, 111, 0, 108, 0, 108, 0, 111, 0, 119, 0, 101, 0, 100, 0, 32, 0, 98, 0, 121, 0, 32, 0, 116, 0, 104, 0, 101, 0, 32, 0, 110, 0, 101, 0, 119, 0, 32, 0, 112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 33 });
return true;
}
if (!Auth.isRegistered(client.username)) {
// register first...
client.conn.send(new byte[] { 3, 0, 60, 0, (byte) 167, 0, 57, 0, 89, 0, 111, 0, 117, 0, 32, 0, 109, 0, 117, 0, 115, 0, 116, 0, 32, 0, 114, 0, 101, 0, 103, 0, 105, 0, 115, 0, 116, 0, 101, 0, 114, 0, 32, 0, 97, 0, 110, 0, 32, 0, 97, 0, 99, 0, 99, 0, 111, 0, 117, 0, 110, 0, 116, 0, 32, 0, 102, 0, 105, 0, 114, 0, 115, 0, 116, 0, 32, 0, 116, 0, 111, 0, 32, 0, 99, 0, 104, 0, 97, 0, 110, 0, 103, 0, 101, 0, 32, 0, 105, 0, 116, 0, 115, 0, 32, 0, 112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 33 });
return true;
}
if (!Auth.login(client.username, firstArg.toCharArray())) {
// invalid old password
client.conn.send(new byte[] { 3, 0, 29, 0, (byte) 167, 0, 57, 0, 84, 0, 104, 0, 97, 0, 116, 0, 32, 0, 112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 32, 0, 105, 0, 115, 0, 32, 0, 105, 0, 110, 0, 99, 0, 111, 0, 114, 0, 114, 0, 101, 0, 99, 0, 116, 0, 33 });
return true;
}
if (Auth.changePass(client.username, secondArg.toCharArray())) {
// changed password!
Main.printMsg("Player " + client + " changed their password!");
client.conn.send(new byte[] { 3, 0, 33, 0, (byte) 167, 0, 57, 0, 89, 0, 111, 0, 117, 0, 114, 0, 32, 0, 112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 32, 0, 104, 0, 97, 0, 115, 0, 32, 0, 98, 0, 101, 0, 101, 0, 110, 0, 32, 0, 99, 0, 104, 0, 97, 0, 110, 0, 103, 0, 101, 0, 100, 0, 33 });
}
break;
*/
default:
return false;
}

View File

@ -28,9 +28,14 @@ public class Client {
public boolean hasLoginHappened = false;
public boolean authed = !Main.useAuth;
public int clientEntityId;
public int serverEntityId;
public List<byte[]> packetCache = new ArrayList<>();
public byte[] positionPacket = null;
public void setSocket(Socket sock) throws IOException {
socket = sock;
socketOut = sock.getOutputStream();

View File

@ -24,6 +24,11 @@ public class Main {
public static boolean forwarded = false;
public static boolean filterUsernames = true;
public static boolean useAuth = false;
public static int authIpLimit = -1;
public static WebSocketServer webSocketServer = null;
public static Map<WebSocket, Client> clients = new HashMap<>();
@ -112,6 +117,19 @@ public class Main {
}
}
Map<String, Object> configAuth = new HashMap<>();
configAuth.put("enabled", false);
configAuth.put("ip_limit", -1);
configAuth = (LinkedHashMap<String, Object>) config.getOrDefault("auth", configAuth);
useAuth = (boolean) configAuth.getOrDefault("enabled", false);
if (useAuth) {
authIpLimit = (int) configAuth.getOrDefault("ip_limit", 0);
Auth.readDatabase();
}
webPort = (int) config.getOrDefault("web_port", 25565);
forwarded = (boolean) config.getOrDefault("forwarded", false);
@ -279,6 +297,10 @@ public class Main {
break;
}
*/
if (!targetUser.authed) {
printMsg("That user is not yet authenticated!");
break;
}
try {
int destServer = Integer.parseInt(pieces[2]);
targetUser.server = Math.max(0, Math.min(servers.size() - 1, destServer));

View File

@ -14,6 +14,7 @@ public class PluginMessages {
try {
String bungeeTag = dataIn.readUTF();
if (bungeeTag.equals("Connect")) { // actually send current player to server :D
if (!client.authed) return true;
String destServer = dataIn.readUTF();
try {
int destServerInt = Integer.parseInt(destServer);

View File

@ -53,9 +53,9 @@ public class WebSocketProxy extends WebSocketServer {
}
new Thread(() -> {
try {
Thread.sleep(5000);
Thread.sleep(10000);
} catch (InterruptedException ignored) {}
if (conn.isOpen() && (Main.bans.contains(Main.getIp(conn)) || !Main.clients.containsKey(conn))) conn.close();
if (conn.isOpen() && (Main.bans.contains(Main.getIp(conn)) || !Main.clients.containsKey(conn) || !Main.clients.get(conn).authed)) conn.close();
}).start();
}
@ -113,6 +113,10 @@ public class WebSocketProxy extends WebSocketServer {
StringBuilder unameBuilder = new StringBuilder();
for (int i = 0; i < unameLen; i++) unameBuilder.append(message.getChar());
String username = unameBuilder.toString();
if (!username.equals(username.replaceAll("[^A-Za-z0-9_-]", "_"))) {
conn.close();
return;
}
if (Main.clients.values().stream().anyMatch(client -> client.username.equals(username) || client.conn == conn)) {
conn.close();
return;
@ -123,9 +127,16 @@ public class WebSocketProxy extends WebSocketServer {
try {
while (conn.isOpen()) {
int currServer = selfClient.server;
if (currServer == -1 && selfClient.authed) currServer = selfClient.server = 0;
selfClient.hasLoginHappened = false;
if (!selfClient.firstTime) Main.printMsg("Player " + selfClient + " joined server " + selfClient.server + "!");
if (!selfClient.firstTime) Main.printMsg("Player " + selfClient + " joined server " + currServer + "!");
ServerItem chosenServer = Main.servers.get(currServer);
/*
if (chosenServer.host.equals(Main.authKey)) {
// todo: custom server here
return;
}
*/
Socket selfSocket = new Socket();
try {
// todo: pregenerate InetSocketAddresses
@ -154,6 +165,7 @@ public class WebSocketProxy extends WebSocketServer {
}
if (ChatHandler.serverChatMessage(selfClient, data)) continue;
if (PluginMessages.serverPluginMessage(selfClient, data)) continue;
if (!selfClient.authed && data[0] == 13) selfClient.positionPacket = data;
boolean loginPacket = data[0] == 1;
if (loginPacket && !selfClient.hasLoginHappened) selfClient.hasLoginHappened = true;
if (selfClient.firstTime && loginPacket) selfClient.clientEntityId = selfClient.serverEntityId = EntityMap.readInt(data, 1);
@ -182,7 +194,17 @@ public class WebSocketProxy extends WebSocketServer {
if (conn.isOpen()) conn.send(new byte[] { 9, 0, 0, 0, -1, 0, 0, 1, 0, 0, 7, 0, 100, 0, 101, 0, 102, 0, 97, 0, 117, 0, 108, 0, 116 });
}
EntityMap.rewrite(data, selfClient.serverEntityId, selfClient.clientEntityId);
if (conn.isOpen()) conn.send(data);
if (selfClient.authed || ((loginPacket || data[0] == 51 || data[0] == 13 || data[0] == 6) || !selfClient.hasLoginHappened)) {
if (selfClient.authed) {
while (selfClient.packetCache.size() > 0) {
if (conn.isOpen()) conn.send(selfClient.packetCache.remove(0));
}
}
if (conn.isOpen()) conn.send(data);
} else {
// cache data
selfClient.packetCache.add(data);
}
if (loginPacket) sendToServer(new byte[] { (byte) 250, 0, 8, 0, 82, 0, 69, 0, 71, 0, 73, 0, 83, 0, 84, 0, 69, 0, 82, 0, 10, 66, 117, 110, 103, 101, 101, 67, 111, 114, 100 }, selfClient);
}
if (conn.isOpen() && selfClient.server == currServer) conn.close();
@ -216,6 +238,13 @@ public class WebSocketProxy extends WebSocketServer {
}
public void sendToServer(byte[] orig, Client client) throws IOException {
if (!client.authed) {
if (orig.length > 0) {
if (orig[0] == 2 || orig[0] == 3) client.conn.send(new byte[] { 3, 0, 114, 0, (byte) 167, 0, 57, 0, 80, 0, 108, 0, 101, 0, 97, 0, 115, 0, 101, 0, 32, 0, 114, 0, 101, 0, 103, 0, 105, 0, 115, 0, 116, 0, 101, 0, 114, 0, 32, 0, 111, 0, 114, 0, 32, 0, 108, 0, 111, 0, 103, 0, 105, 0, 110, 0, 32, 0, 116, 0, 111, 0, 32, 0, 99, 0, 111, 0, 110, 0, 116, 0, 105, 0, 110, 0, 117, 0, 101, 0, 32, 0, 116, 0, 111, 0, 32, 0, 116, 0, 104, 0, 105, 0, 115, 0, 32, 0, 115, 0, 101, 0, 114, 0, 118, 0, 101, 0, 114, 0, 33, 0, 32, 0, 47, 0, 114, 0, 101, 0, 103, 0, 105, 0, 115, 0, 116, 0, 101, 0, 114, 0, 32, 0, 60, 0, 112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 62, 0, 32, 0, 60, 0, 99, 0, 111, 0, 110, 0, 102, 0, 105, 0, 114, 0, 109, 0, 80, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 62, 0, 32, 0, 111, 0, 114, 0, 32, 0, 47, 0, 108, 0, 111, 0, 103, 0, 105, 0, 110, 0, 32, 0, 60, 0, 112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 62 });
if (orig[0] == 10 || orig[0] == 11 || orig[0] == 12 || orig[0] == 13 || orig[0] == 14 || orig[0] == 15) client.conn.send(client.positionPacket);
}
if (client.hasLoginHappened) return; // drop client packets :trol:
}
byte[] data = orig.clone();
EntityMap.rewrite(data, client.clientEntityId, client.serverEntityId);
client.socketOut.write(data);

View File

@ -11,6 +11,12 @@ origin_blacklist: "https://g.eags.us/eaglercraft/origin_blacklist.txt"
# for example,
# - "https://g.eags.us"
origins: []
# authentication info
auth:
# use auth
enabled: false
# max registers per ip (set to 0 for unlimited, not recommended, but necessary for servers that cannot see user IPs)
ip_limit: 0
# motd info
motd:
# the motd itself