DB update, protocol update

This commit is contained in:
Michael Wain 2025-03-19 20:54:06 +03:00
parent ac08cc0f8a
commit 63d68114e7
14 changed files with 452 additions and 106 deletions

33
pom.xml
View File

@ -38,11 +38,42 @@
<artifactId>jbcrypt</artifactId>
<version>0.4</version>
</dependency>
<dependency>
<groupId>com.jsoniter</groupId>
<artifactId>jsoniter</artifactId>
<version>0.9.23</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mapdb</groupId>
<artifactId>mapdb</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>

View File

@ -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<String, Boolean> sessions;
private final UserStorage storage;
private final Map<String, Boolean> 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<String, String> 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<String, String> 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<MinecraftProfileTexture.Type, MinecraftProfileTexture> 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<MinecraftProperty> 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<String, String> 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<String, String> 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();
}
}

View File

@ -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();
}
}
}

View File

@ -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());
}
}
}

View File

@ -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
}
}

View File

@ -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<String, String> 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);
}
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<MinecraftProperty> properties;
}

View File

@ -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<String, String> metadata;
public enum Type {
SKIN,
CAPE;
}
}

View File

@ -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;
}

View File

@ -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<MinecraftProfileTexture.Type, MinecraftProfileTexture> textures;
}

View File

@ -0,0 +1,10 @@
package com.alterdekim.xcraft.auth.response;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public class SignUpResponse {
private String uuid;
}

View File

@ -0,0 +1,3 @@
public_domain: "localhost"
public_port: 8999
use_https: false