DB update, protocol update
This commit is contained in:
parent
ac08cc0f8a
commit
63d68114e7
33
pom.xml
33
pom.xml
@ -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>
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
20
src/main/java/com/alterdekim/xcraft/auth/database/User.java
Normal file
20
src/main/java/com/alterdekim/xcraft/auth/database/User.java
Normal 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
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.alterdekim.xcraft.auth.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public class SignUpResponse {
|
||||
private String uuid;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
public_domain: "localhost"
|
||||
public_port: 8999
|
||||
use_https: false
|
Loading…
x
Reference in New Issue
Block a user