diff --git a/pom.xml b/pom.xml index b1930c8..66abcc9 100644 --- a/pom.xml +++ b/pom.xml @@ -38,11 +38,42 @@ jbcrypt 0.4 - + + com.jsoniter + jsoniter + 0.9.23 + + + org.projectlombok + lombok + 1.18.36 + provided + + + org.mapdb + mapdb + 3.0.0 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + org.projectlombok + lombok + 1.18.36 + + + + maven-assembly-plugin diff --git a/src/main/java/com/alterdekim/xcraft/auth/SaltNic.java b/src/main/java/com/alterdekim/xcraft/auth/SaltNic.java index 77d808d..b77a720 100644 --- a/src/main/java/com/alterdekim/xcraft/auth/SaltNic.java +++ b/src/main/java/com/alterdekim/xcraft/auth/SaltNic.java @@ -1,29 +1,47 @@ package com.alterdekim.xcraft.auth; +import com.alterdekim.xcraft.auth.database.User; +import com.alterdekim.xcraft.auth.database.UserStorage; +import com.alterdekim.xcraft.auth.request.JoinMinecraftServerRequest; +import com.alterdekim.xcraft.auth.request.SignUpRequest; +import com.alterdekim.xcraft.auth.response.*; +import com.jsoniter.JsonIterator; +import com.jsoniter.output.JsonStream; import fi.iki.elonen.NanoHTTPD; -import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; -import org.mindrot.jbcrypt.BCrypt; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.security.NoSuchAlgorithmException; -import java.util.HashMap; -import java.util.Map; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.logging.Logger; +import static com.alterdekim.xcraft.auth.XCraft.PUBLIC_DOMAIN; import static com.alterdekim.xcraft.auth.XCraft.SERVER_PORT; public class SaltNic extends NanoHTTPD { private final Logger logger; + private final ConcurrentMap sessions; + private final UserStorage storage; - private final Map sessions; + private final Response invalidSession = newFixedLengthResponse(Response.Status.FORBIDDEN, "application/json", "{\"error\":\"ForbiddenOperationException\", \"errorMessage\":\"Invalid session token\", \"cause\": \"ForbiddenOperationException\"}"); + + private static final String SKIN_DIRECTORY = "plugins/XCraftAuth/skins"; + private static final String CAPE_DIRECTORY = "plugins/XCraftAuth/capes"; + private static final int MAX_FILE_SIZE = 1024 * 1024; public SaltNic(Logger logger) throws IOException { super(SERVER_PORT); this.logger = logger; - this.sessions = new HashMap<>(); + this.storage = new UserStorage(); + this.sessions = new ConcurrentHashMap<>(); start(NanoHTTPD.SOCKET_READ_TIMEOUT, false); logger.info("SaltNic session server started on http://localhost:"+SERVER_PORT); } @@ -42,23 +60,192 @@ public class SaltNic extends NanoHTTPD { return handleProfileRequest(session, uri); } else if (uri.startsWith("/api/register") && method == Method.POST) { return handleProfileRegistration(session); + } else if (Method.POST == method && "/api/set_model".equals(uri)) { + return handleSetModel(session); + } else if (Method.POST == method && "/api/upload_cape".equals(uri)) { + return handleCapeUpload(session); + } else if (Method.POST == method && "/api/upload".equals(uri)) { + return handleSkinUpload(session); + } else if (Method.GET == method && uri.startsWith("/api/skin/s")) { + return serveSkinImage(uri.substring(11)); + } else if (Method.GET == method && uri.startsWith("/api/cape/a")) { + return serveCapeImage(uri.substring(11)); } return newFixedLengthResponse(Response.Status.NOT_FOUND, "text/plain", "404 Not Found"); } + private Response handleSkinUpload(IHTTPSession session) { + try { + // Parse multipart form data + Map files = new java.util.HashMap<>(); + session.parseBody(files); + String playerUUID = session.getParameters().get("uuid").get(0); + String password = session.getParameters().get("password").get(0); + + if (playerUUID == null || password == null || !files.containsKey("skin")) { + return newFixedLengthResponse(Response.Status.OK, "text/plain", "Missing player credentials or skin file"); + } + + if( this.storage.getUserPassword(playerUUID) == null || !PasswordHasher.checkPassword(password, this.storage.getUserPassword(playerUUID)) ) { + return newFixedLengthResponse(Response.Status.OK, "text/plain", "Invalid credentials"); + } + + new File(SKIN_DIRECTORY).mkdirs(); + + File tempFile = new File(files.get("skin")); + + long fileSize = tempFile.length(); + if (fileSize > MAX_FILE_SIZE) { + return newFixedLengthResponse(Response.Status.OK, "text/plain", "File is too large. Max size: 1MB"); + } + + BufferedImage image = ImageIO.read(tempFile); + if (image == null) { + return newFixedLengthResponse(Response.Status.OK, "text/plain", "Invalid image file"); + } + + int width = image.getWidth(); + int height = image.getHeight(); + + if (!((width == 64 && height == 64) || (width == 64 && height == 32))) { + return newFixedLengthResponse(Response.Status.OK, "text/plain", "Invalid dimensions. Only 64x64 or 64x32 allowed"); + } + + File skinFile = new File(SKIN_DIRECTORY, playerUUID + ".png"); + + Files.copy(tempFile.toPath(), skinFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + return newFixedLengthResponse(Response.Status.OK, "text/plain", "Skin uploaded successfully for " + playerUUID); + } catch (Exception e) { + e.printStackTrace(); + return newFixedLengthResponse(Response.Status.OK, "text/plain", "Upload failed: " + e.getMessage()); + } + } + + private Response handleCapeUpload(IHTTPSession session) { + try { + // Parse multipart form data + Map files = new java.util.HashMap<>(); + session.parseBody(files); + String playerUUID = session.getParameters().get("uuid").get(0); + String password = session.getParameters().get("password").get(0); + + if (playerUUID == null || password == null || !files.containsKey("cape")) { + return newFixedLengthResponse(Response.Status.OK, "text/plain", "Missing player credentials or skin file"); + } + + if( this.storage.getUserPassword(playerUUID) == null || !PasswordHasher.checkPassword(password, this.storage.getUserPassword(playerUUID)) ) { + return newFixedLengthResponse(Response.Status.OK, "text/plain", "Invalid credentials"); + } + + new File(CAPE_DIRECTORY).mkdirs(); + + File tempFile = new File(files.get("cape")); + + long fileSize = tempFile.length(); + if (fileSize > MAX_FILE_SIZE) { + return newFixedLengthResponse(Response.Status.OK, "text/plain", "File is too large. Max size: 1MB"); + } + + BufferedImage image = ImageIO.read(tempFile); + if (image == null) { + return newFixedLengthResponse(Response.Status.OK, "text/plain", "Invalid image file"); + } + + int width = image.getWidth(); + int height = image.getHeight(); + + if (!(width == 64 && height == 32)) { + return newFixedLengthResponse(Response.Status.OK, "text/plain", "Invalid dimensions. Only 64x32 allowed"); + } + + File capeFile = new File(CAPE_DIRECTORY, playerUUID + ".png"); + + Files.copy(tempFile.toPath(), capeFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + return newFixedLengthResponse(Response.Status.OK, "text/plain", "Cape uploaded successfully for " + playerUUID); + } catch (Exception e) { + e.printStackTrace(); + return newFixedLengthResponse(Response.Status.OK, "text/plain", "Upload failed: " + e.getMessage()); + } + } + + private Response handleSetModel(IHTTPSession session) { + try { + String playerUUID = session.getParameters().get("uuid").get(0); + String password = session.getParameters().get("password").get(0); + Boolean model = Boolean.parseBoolean(session.getParameters().get("model").get(0)); + + if (playerUUID == null || password == null) { + return newFixedLengthResponse(Response.Status.OK, "text/plain", "Missing player credentials"); + } + + if( this.storage.getUserPassword(playerUUID) == null || !PasswordHasher.checkPassword(password, this.storage.getUserPassword(playerUUID)) ) { + return newFixedLengthResponse(Response.Status.OK, "text/plain", "Invalid credentials"); + } + + this.storage.setSkinModel(playerUUID, model ? User.SkinModel.Alex : User.SkinModel.Steve); + + return newFixedLengthResponse(Response.Status.OK, "text/plain", "Skin model was set successfully for " + playerUUID); + } catch (Exception e) { + e.printStackTrace(); + return newFixedLengthResponse(Response.Status.OK, "text/plain", "Setting failed: " + e.getMessage()); + } + } + + private Response serveCapeImage(String playerName) { + File skinFile = new File(CAPE_DIRECTORY, playerName + ".png"); + + if (!skinFile.exists()) { + return newFixedLengthResponse(Response.Status.NOT_FOUND, "text/plain", "Cape not found for " + playerName); + } + try { + FileInputStream fis = new FileInputStream(skinFile); + return newChunkedResponse(Response.Status.OK, "image/png", fis); + } catch (IOException e) { + return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, "text/plain", "Failed to load skin"); + } + } + + private Response serveSkinImage(String playerName) { + File skinFile = new File(SKIN_DIRECTORY, playerName + ".png"); + + if (!skinFile.exists()) { + return newFixedLengthResponse(Response.Status.NOT_FOUND, "text/plain", "Skin not found for " + playerName); + } + try { + FileInputStream fis = new FileInputStream(skinFile); + return newChunkedResponse(Response.Status.OK, "image/png", fis); + } catch (IOException e) { + return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, "text/plain", "Failed to load skin"); + } + } + + private MinecraftProperty getTextures(String uuid) { + Map textures = new HashMap<>(); + + if( new File(SKIN_DIRECTORY, uuid + ".png").exists() ) { + MinecraftProfileTexture texture = new MinecraftProfileTexture("http://"+PUBLIC_DOMAIN+":"+SERVER_PORT+"/api/skin/s" + uuid, new HashMap<>()); + if( storage.getSkinModel(uuid) == User.SkinModel.Alex ) { + texture.getMetadata().put("model", "slim"); + } + textures.put(MinecraftProfileTexture.Type.SKIN, texture); + } + + if( new File(CAPE_DIRECTORY, uuid + ".png").exists() ) { + textures.put(MinecraftProfileTexture.Type.CAPE, new MinecraftProfileTexture("http://"+PUBLIC_DOMAIN+":"+SERVER_PORT+"/api/cape/a" + uuid, new HashMap<>())); + } + + MinecraftTexturesPayload minecraftTexturesPayload = new MinecraftTexturesPayload(System.currentTimeMillis(), uuid, this.storage.getUsername(uuid), textures); + return new MinecraftProperty("textures", Base64.getEncoder().encodeToString(JsonStream.serialize(minecraftTexturesPayload).getBytes())); + } + private Response handleProfileRequest(IHTTPSession session, String uri) { if( uri.length() != 45 ) return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, "text/plain", "Server error"); String uuid = uri.substring(13); - if( UserStorage.getUserPassword(uuid) == null ) return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, "text/plain", "Server error"); - return newFixedLengthResponse(Response.Status.OK, "application/json", "{\n" + - " \"id\" : \""+uuid+"\",\n" + - " \"name\" : \"Notch\",\n" + - " \"properties\" : [ {\n" + - " \"name\" : \"textures\",\n" + - " \"value\" : \"ewogICJ0aW1lc3RhbXAiIDogMTc0MjA1ODQ1MDI1MywKICAicHJvZmlsZUlkIiA6ICJmYzE0MzZmZmQ3MDA0NWFmOWMxODNkZjhjODMwMmU5ZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJEYXJ0SmV2ZGVyIiwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzVlMTM1Y2ZkYTgwM2U3ZDQ4NTNhN2M5YjQ5N2JhZjM3YWNlNmZkZGYyYjYyNDI1MWY3YjkwNmYyOTAwZWRiMyIsCiAgICAgICJtZXRhZGF0YSIgOiB7CiAgICAgICAgIm1vZGVsIiA6ICJzbGltIgogICAgICB9CiAgICB9LAogICAgIkNBUEUiIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2EyZThkOTdlYzc5MTAwZTkwYTc1ZDM2OWQxYjNiYTgxMjczYzRmODJiYzFiNzM3ZTkzNGVlZDRhODU0YmUxYjYiCiAgICB9CiAgfQp9\"\n" + - " } ],\n" + - " \"profileActions\" : [ ]\n" + - "}"); + if( this.storage.getUserPassword(uuid) == null ) return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, "text/plain", "Server error"); + List properties = Collections.singletonList(getTextures(uuid)); + String username = this.storage.getUsername(uuid); + MinecraftProfilePropertiesResponse profile = new MinecraftProfilePropertiesResponse(uuid, username, properties); + return newFixedLengthResponse(Response.Status.OK, "application/json", JsonStream.serialize(profile)); } private Response handleHasJoinedRequest(IHTTPSession session) { @@ -66,34 +253,26 @@ public class SaltNic extends NanoHTTPD { String uuid = UserId.generateUserId(session.getParameters().get("username").get(0)); if (this.sessions.containsKey(uuid) && this.sessions.get(uuid)) { this.sessions.remove(uuid); - return newFixedLengthResponse(Response.Status.OK, "application/json", "{\n" + - " \"id\" : \"" + uuid + "\",\n" + - " \"name\" : \"" + session.getParameters().get("username").get(0) + "\",\n" + - " \"properties\" : [ {\n" + - " \"name\" : \"textures\",\n" + - " \"value\" : \"ewogICJ0aW1lc3RhbXAiIDogMTc0MjA1ODQ1MDI1MywKICAicHJvZmlsZUlkIiA6ICJmYzE0MzZmZmQ3MDA0NWFmOWMxODNkZjhjODMwMmU5ZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJEYXJ0SmV2ZGVyIiwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzVlMTM1Y2ZkYTgwM2U3ZDQ4NTNhN2M5YjQ5N2JhZjM3YWNlNmZkZGYyYjYyNDI1MWY3YjkwNmYyOTAwZWRiMyIsCiAgICAgICJtZXRhZGF0YSIgOiB7CiAgICAgICAgIm1vZGVsIiA6ICJzbGltIgogICAgICB9CiAgICB9LAogICAgIkNBUEUiIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2EyZThkOTdlYzc5MTAwZTkwYTc1ZDM2OWQxYjNiYTgxMjczYzRmODJiYzFiNzM3ZTkzNGVlZDRhODU0YmUxYjYiCiAgICB9CiAgfQp9\"\n" + - " } ],\n" + - " \"profileActions\" : [ ]\n" + - "}"); + return handleProfileRequest(session, "/api/profile/"+uuid); } } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } - return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, "text/plain", "Server error"); + return invalidSession; } private Response handleProfileRegistration(IHTTPSession session) { try { Map files = new HashMap<>(); session.parseBody(files); - JSONObject json = parseJSON(files.get("postData")); + SignUpRequest request = JsonIterator.deserialize(files.get("postData"), SignUpRequest.class); - if (json == null) { + if (request == null) { return newFixedLengthResponse(Response.Status.BAD_REQUEST, "text/plain", "Invalid JSON format"); } - String username = (String) json.get("username"); - String password = (String) json.get("password"); + String username = request.getUsername(); + String password = request.getPassword(); if (username == null || password == null || password.length() < 3 || username.length() < 3) { return newFixedLengthResponse(Response.Status.BAD_REQUEST, "text/plain", "Missing username or password"); @@ -101,17 +280,15 @@ public class SaltNic extends NanoHTTPD { String uuid = UserId.generateUserId(username); - if( UserStorage.getUserPassword(uuid) != null ) { + if( this.storage.getUserPassword(uuid) != null ) { return newFixedLengthResponse(Response.Status.CONFLICT, "text/plain", "User already exists"); } - UserStorage.saveUser(uuid, PasswordHasher.hashPassword(password)); + this.storage.saveUser(uuid, new User(username, PasswordHasher.hashPassword(password), User.SkinModel.Steve)); - JSONObject response = new JSONObject(); - response.put("uuid", uuid); - return newFixedLengthResponse(Response.Status.OK, "text/plain", response.toJSONString()); + return newFixedLengthResponse(Response.Status.OK, "text/plain", JsonStream.serialize(new SignUpResponse(uuid))); } catch (Exception e) { - logger.warning("Error while processing sign up request from client: " + e.getMessage()); + logger.info("Error while processing sign up request from client: " + e.getMessage()); return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, "text/plain", "Server error: " + e.getMessage()); } } @@ -120,41 +297,37 @@ public class SaltNic extends NanoHTTPD { try { Map files = new HashMap<>(); session.parseBody(files); - JSONObject json = parseJSON(files.get("postData")); + JoinMinecraftServerRequest joinRequest = JsonIterator.deserialize(files.get("postData"), JoinMinecraftServerRequest.class); - if (json == null) { - return newFixedLengthResponse(Response.Status.BAD_REQUEST, "text/plain", "Invalid JSON format"); + if (joinRequest == null) { + return invalidSession; } - String username = (String) json.get("selectedProfile"); - String sessionToken = (String) json.get("accessToken"); + String username = joinRequest.getSelectedProfile(); + String sessionToken = joinRequest.getAccessToken(); if (username == null || sessionToken == null) { - return newFixedLengthResponse(Response.Status.BAD_REQUEST, "text/plain", "Missing selectedProfile or accessToken"); + return invalidSession; } - boolean validSession = PasswordHasher.checkPassword(sessionToken, UserStorage.getUserPassword(username)); + boolean validSession = PasswordHasher.checkPassword(sessionToken, this.storage.getUserPassword(username)); if (validSession) { this.sessions.put(username, true); return newFixedLengthResponse(Response.Status.OK, "application/json", "{}"); } else { this.sessions.put(username, false); - return newFixedLengthResponse(Response.Status.UNAUTHORIZED, "application/json","{\"status\":\"error\", \"message\":\"Invalid session token\"}"); + return invalidSession; } } catch (Exception e) { - logger.warning("Error while processing join request from client: " + e.getMessage()); - return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, "text/plain", "Server error: " + e.getMessage()); + logger.info("Error while processing join request from client: " + e.getMessage()); + return invalidSession; } } - private JSONObject parseJSON(String jsonData) { - try { - JSONParser parser = new JSONParser(); - return (JSONObject) parser.parse(jsonData); - } catch (ParseException e) { - e.printStackTrace(); - return null; - } + @Override + public void stop() { + super.stop(); + this.storage.close(); } } diff --git a/src/main/java/com/alterdekim/xcraft/auth/UserStorage.java b/src/main/java/com/alterdekim/xcraft/auth/UserStorage.java deleted file mode 100644 index 5ec0528..0000000 --- a/src/main/java/com/alterdekim/xcraft/auth/UserStorage.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.alterdekim.xcraft.auth; - -import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; - -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; - -public class UserStorage { - private static final File USER_FILE = new File("plugins/XCraftAuth/users.json"); - - public static void saveUser(String username, String hashedPassword) { - JSONObject users = loadUsers(); - users.put(username, hashedPassword); - - try (FileWriter writer = new FileWriter(USER_FILE)) { - writer.write(users.toJSONString()); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public static String getUserPassword(String username) { - JSONObject users = loadUsers(); - return (String) users.get(username); - } - - private static JSONObject loadUsers() { - if (!USER_FILE.exists()) { - return new JSONObject(); - } - - try (FileReader reader = new FileReader(USER_FILE)) { - JSONParser parser = new JSONParser(); - return (JSONObject) parser.parse(reader); - } catch (IOException | ParseException e) { - return new JSONObject(); - } - } -} diff --git a/src/main/java/com/alterdekim/xcraft/auth/XCraft.java b/src/main/java/com/alterdekim/xcraft/auth/XCraft.java index 3f40a43..9e9dfc1 100644 --- a/src/main/java/com/alterdekim/xcraft/auth/XCraft.java +++ b/src/main/java/com/alterdekim/xcraft/auth/XCraft.java @@ -12,7 +12,9 @@ public class XCraft extends JavaPlugin { private static SaltNic server = null; - public static final int SERVER_PORT = 8999; + public static int SERVER_PORT = 8999; + public static String PUBLIC_DOMAIN = "localhost"; + public static Boolean USE_HTTPS = false; @Override public void onEnable() { @@ -20,17 +22,24 @@ public class XCraft extends JavaPlugin { if( server == null ) { try { getLogger().info("Starting SaltNic server..."); + SERVER_PORT = getConfig().getInt("public_port"); + PUBLIC_DOMAIN = getConfig().getString("public_domain"); + USE_HTTPS = getConfig().getBoolean("use_https"); server = new SaltNic(getLogger()); } catch (IOException e) { getLogger().severe("Failed to start SaltNic server: " + e.getMessage()); } } getLogger().info("Patching AuthLib URLs..."); - try { - patchAuthLib(); - getLogger().info("AuthLib URLs patched successfully!"); - } catch (Exception e) { - getLogger().severe("Failed to patch AuthLib: " + e.getMessage()); + while(true) { + try { + patchAuthLib(); + getLogger().info("AuthLib URLs patched successfully!"); + return; + } catch (Exception e) { + e.printStackTrace(); + getLogger().severe("Failed to patch AuthLib: " + e.getMessage()); + } } } diff --git a/src/main/java/com/alterdekim/xcraft/auth/database/User.java b/src/main/java/com/alterdekim/xcraft/auth/database/User.java new file mode 100644 index 0000000..de9aba5 --- /dev/null +++ b/src/main/java/com/alterdekim/xcraft/auth/database/User.java @@ -0,0 +1,20 @@ +package com.alterdekim.xcraft.auth.database; + +import lombok.*; + +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class User { + private String username; + private String password; + private SkinModel model; + + @Getter + @RequiredArgsConstructor + public enum SkinModel { + Steve, + Alex + } +} diff --git a/src/main/java/com/alterdekim/xcraft/auth/database/UserStorage.java b/src/main/java/com/alterdekim/xcraft/auth/database/UserStorage.java new file mode 100644 index 0000000..dc43a64 --- /dev/null +++ b/src/main/java/com/alterdekim/xcraft/auth/database/UserStorage.java @@ -0,0 +1,56 @@ +package com.alterdekim.xcraft.auth.database; + +import com.jsoniter.JsonIterator; +import com.jsoniter.output.JsonStream; +import org.mapdb.DB; +import org.mapdb.DBMaker; +import org.mapdb.Serializer; + +import java.io.File; +import java.util.concurrent.ConcurrentMap; + +public class UserStorage { + private static final File DB_FILE = new File("plugins/XCraftAuth/users.db"); + private final DB db = DBMaker.fileDB(DB_FILE).fileMmapEnable().make(); + private final ConcurrentMap users = db.hashMap("users", Serializer.STRING, Serializer.STRING).createOrOpen(); + + public void saveUser(String uuid, User user) { + this.users.put(uuid, JsonStream.serialize(user)); + } + + public User.SkinModel getSkinModel(String uuid) { + if( this.users.containsKey(uuid) ) { + User user = JsonIterator.deserialize(this.users.get(uuid), User.class); + return user.getModel(); + } + return User.SkinModel.Steve; + } + + public String getUsername(String uuid) { + if( this.users.containsKey(uuid) ) { + User user = JsonIterator.deserialize(this.users.get(uuid), User.class); + return user.getUsername(); + } + return null; + } + + public String getUserPassword(String uuid) { + if (this.users.containsKey(uuid)) { + User user = JsonIterator.deserialize(this.users.get(uuid), User.class); + return user.getPassword(); + } + return null; + } + + public void close() { + db.close(); + } + + public void setSkinModel(String uuid, User.SkinModel skinModel) { + if (this.users.containsKey(uuid)) { + User user = JsonIterator.deserialize(this.users.get(uuid), User.class); + user.setModel(skinModel); + this.saveUser(uuid, user); + } + } +} diff --git a/src/main/java/com/alterdekim/xcraft/auth/request/JoinMinecraftServerRequest.java b/src/main/java/com/alterdekim/xcraft/auth/request/JoinMinecraftServerRequest.java new file mode 100644 index 0000000..79b48f8 --- /dev/null +++ b/src/main/java/com/alterdekim/xcraft/auth/request/JoinMinecraftServerRequest.java @@ -0,0 +1,14 @@ +package com.alterdekim.xcraft.auth.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@Getter +@NoArgsConstructor +public class JoinMinecraftServerRequest { + public String accessToken; + public String selectedProfile; + public String serverId; +} diff --git a/src/main/java/com/alterdekim/xcraft/auth/request/SignUpRequest.java b/src/main/java/com/alterdekim/xcraft/auth/request/SignUpRequest.java new file mode 100644 index 0000000..565b936 --- /dev/null +++ b/src/main/java/com/alterdekim/xcraft/auth/request/SignUpRequest.java @@ -0,0 +1,13 @@ +package com.alterdekim.xcraft.auth.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class SignUpRequest { + private String username; + private String password; +} diff --git a/src/main/java/com/alterdekim/xcraft/auth/response/MinecraftProfilePropertiesResponse.java b/src/main/java/com/alterdekim/xcraft/auth/response/MinecraftProfilePropertiesResponse.java new file mode 100644 index 0000000..2cfdde0 --- /dev/null +++ b/src/main/java/com/alterdekim/xcraft/auth/response/MinecraftProfilePropertiesResponse.java @@ -0,0 +1,14 @@ +package com.alterdekim.xcraft.auth.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; + +@AllArgsConstructor +@Getter +public class MinecraftProfilePropertiesResponse { + private String id; + private String name; + private List properties; +} \ No newline at end of file diff --git a/src/main/java/com/alterdekim/xcraft/auth/response/MinecraftProfileTexture.java b/src/main/java/com/alterdekim/xcraft/auth/response/MinecraftProfileTexture.java new file mode 100644 index 0000000..46fa00e --- /dev/null +++ b/src/main/java/com/alterdekim/xcraft/auth/response/MinecraftProfileTexture.java @@ -0,0 +1,20 @@ +package com.alterdekim.xcraft.auth.response; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Map; + + +@Getter +@RequiredArgsConstructor +public class MinecraftProfileTexture { + private final String url; + private final Map metadata; + + public enum Type { + SKIN, + CAPE; + } +} + diff --git a/src/main/java/com/alterdekim/xcraft/auth/response/MinecraftProperty.java b/src/main/java/com/alterdekim/xcraft/auth/response/MinecraftProperty.java new file mode 100644 index 0000000..ea03a89 --- /dev/null +++ b/src/main/java/com/alterdekim/xcraft/auth/response/MinecraftProperty.java @@ -0,0 +1,11 @@ +package com.alterdekim.xcraft.auth.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class MinecraftProperty { + private String name; + private String value; +} diff --git a/src/main/java/com/alterdekim/xcraft/auth/response/MinecraftTexturesPayload.java b/src/main/java/com/alterdekim/xcraft/auth/response/MinecraftTexturesPayload.java new file mode 100644 index 0000000..21f54dd --- /dev/null +++ b/src/main/java/com/alterdekim/xcraft/auth/response/MinecraftTexturesPayload.java @@ -0,0 +1,15 @@ +package com.alterdekim.xcraft.auth.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Map; + +@AllArgsConstructor +@Getter +public class MinecraftTexturesPayload { + private long timestamp; + private String profileId; + private String profileName; + private Map textures; +} \ No newline at end of file diff --git a/src/main/java/com/alterdekim/xcraft/auth/response/SignUpResponse.java b/src/main/java/com/alterdekim/xcraft/auth/response/SignUpResponse.java new file mode 100644 index 0000000..972734f --- /dev/null +++ b/src/main/java/com/alterdekim/xcraft/auth/response/SignUpResponse.java @@ -0,0 +1,10 @@ +package com.alterdekim.xcraft.auth.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class SignUpResponse { + private String uuid; +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index e69de29..f08c3d2 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -0,0 +1,3 @@ +public_domain: "localhost" +public_port: 8999 +use_https: false \ No newline at end of file