Implement Basic Auth for Webserver

This commit is contained in:
Jonas_Jones 2024-03-02 17:04:01 +01:00
parent aa51d85388
commit 54188d7c31
6 changed files with 106 additions and 36 deletions

View file

@ -15,6 +15,7 @@ public class ModConfigs {
public static String WEB_ROOT;
public static String WEB_FILE_ROOT;
public static String WEB_FILE_404;
public static Boolean WEB_REQUIRE_TOKEN;
public static Boolean SERVER_API_ENABLED;
public static Boolean ADV_API_ENABLED;
public static Boolean API_INGAME_COMMAND_ENABLED;
@ -45,6 +46,7 @@ public class ModConfigs {
config.addKeyValuePair(new Pair<>("web.root", "webserver/"), "the root directory of the webserver, starting from the main server directory");
config.addKeyValuePair(new Pair<>("web.file.root", "index.html"), "the name of the html file for the homepage");
config.addKeyValuePair(new Pair<>("web.file.404", "404.html"), "the name of the html file for 404 page");
config.addKeyValuePair(new Pair<>("web.require_token", false), "wether or not you are required to provide a token to access files on the webserver");
config.addKeyValuePair(new Pair<>("web.api", true), "whether or not the webserver api should be enabled or not");
config.addKeyValuePair(new Pair<>("web.api.adv", true), "whether or not the api should expose information such as player coordinates and inventory");
config.addKeyValuePair(new Pair<>("web.api.cmd", true), "whether or not the ingame command to manage tokens should be enabled or not");
@ -59,6 +61,7 @@ public class ModConfigs {
WEB_ROOT = CONFIG.getOrDefault("web.root", "webserver/");
WEB_FILE_ROOT = CONFIG.getOrDefault("web.file.root", "index.html");
WEB_FILE_404 = CONFIG.getOrDefault("web.file.404", "404.html");
WEB_REQUIRE_TOKEN = CONFIG.getOrDefault("web.require_token", false);
SERVER_API_ENABLED = CONFIG.getOrDefault("web.api", true);
ADV_API_ENABLED = CONFIG.getOrDefault("web.api.adv", true);
API_INGAME_COMMAND_ENABLED = CONFIG.getOrDefault("web.api.cmd", true);

View file

@ -8,6 +8,7 @@ import me.jonasjones.mcwebserver.web.api.v1.ApiV1Handler;
import me.jonasjones.mcwebserver.web.api.ApiRequests;
import me.jonasjones.mcwebserver.web.api.ApiRequestsUtil;
import me.jonasjones.mcwebserver.web.api.v2.ApiV2Handler;
import me.jonasjones.mcwebserver.web.api.v2.tokenmgr.TokenManager;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
@ -22,6 +23,7 @@ import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.StringTokenizer;
@ -121,8 +123,8 @@ public class HttpServer implements Runnable {
while ((header = in.readLine()) != null && !header.isEmpty()) {
// Check if the header contains your API token
if (header.startsWith("Authorization: Bearer ")) {
apiToken = header.substring("Authorization: Bearer ".length());
if (header.startsWith("Authorization: Basic ")) {
apiToken = header.substring("Authorization: Basic ".length());
}
}
@ -238,35 +240,68 @@ public class HttpServer implements Runnable {
fileRequested = fileRequested.substring(1);
}
Path file = WEB_ROOT.resolve(fileRequested).toRealPath(LinkOption.NOFOLLOW_LINKS);
if (!file.startsWith(WEB_ROOT)) {
VerboseLogger.warn("Access to file outside root: " + file);
throw new NoSuchFileException(fileRequested);
}
int fileLength = (int) Files.size(file);
int fileExtensionStartIndex = fileRequested.lastIndexOf(".") + 1;
String contentType;
if (fileExtensionStartIndex > 0) {
contentType = mimetypeidentifier.compare(fileRequested.substring(fileExtensionStartIndex));
} else {
contentType = "text/plain";
try {
boolean isAuthorized = false;
if (!ModConfigs.WEB_REQUIRE_TOKEN) {
isAuthorized = true;
} else if (apiToken != null) {
isAuthorized = TokenManager.isTokenValid(apiToken);
}
if (isAuthorized) {
Path file = WEB_ROOT.resolve(fileRequested).toRealPath(LinkOption.NOFOLLOW_LINKS);
if (!file.startsWith(WEB_ROOT)) {
VerboseLogger.warn("Access to file outside root: " + file);
throw new NoSuchFileException(fileRequested);
}
int fileLength = (int) Files.size(file);
int fileExtensionStartIndex = fileRequested.lastIndexOf(".") + 1;
String contentType;
if (fileExtensionStartIndex > 0) {
contentType = mimetypeidentifier.compare(fileRequested.substring(fileExtensionStartIndex));
} else {
contentType = "text/plain";
}
byte[] fileData = readFileData(file);
// send HTTP Headers
dataOut.write(OK);
dataOut.write(HEADERS);
dataOut.write("Date: %s\r\n".formatted(Instant.now()).getBytes(StandardCharsets.UTF_8));
dataOut.write("Content-Type: %s\r\n".formatted(contentType).getBytes(StandardCharsets.UTF_8));
dataOut.write("Content-Length: %s\r\n".formatted(fileLength).getBytes(StandardCharsets.UTF_8));
dataOut.write(CRLF); // blank line between headers and content, very important !
if (method.equals("GET")) { // GET method so we return content
dataOut.write(fileData, 0, fileLength);
dataOut.flush();
}
VerboseLogger.info("File " + fileRequested + " of type " + contentType + " returned");
} else {
dataOut.write("HTTP/1.1 200 OK\r\n".getBytes(StandardCharsets.UTF_8));
dataOut.write("Date: %s\r\n".formatted(Instant.now()).getBytes(StandardCharsets.UTF_8));
dataOut.write("Content-Type: application/json\r\n".getBytes(StandardCharsets.UTF_8));
String jsonString = ErrorHandler.unauthorizedString();
byte[] jsonBytes = jsonString.getBytes(StandardCharsets.UTF_8);
int contentLength = jsonBytes.length;
dataOut.write(("Content-Length: " + contentLength + "\r\n").getBytes(StandardCharsets.UTF_8));
dataOut.write("\r\n".getBytes(StandardCharsets.UTF_8)); // Blank line before content
// Send JSON data
dataOut.write(jsonBytes, 0, contentLength);
dataOut.flush();
}
} catch (NoSuchAlgorithmException e) {
McWebserver.LOGGER.error("Error getting JSON data from ApiHandler: " + e.getMessage());
}
byte[] fileData = readFileData(file);
// send HTTP Headers
dataOut.write(OK);
dataOut.write(HEADERS);
dataOut.write("Date: %s\r\n".formatted(Instant.now()).getBytes(StandardCharsets.UTF_8));
dataOut.write("Content-Type: %s\r\n".formatted(contentType).getBytes(StandardCharsets.UTF_8));
dataOut.write("Content-Length: %s\r\n".formatted(fileLength).getBytes(StandardCharsets.UTF_8));
dataOut.write(CRLF); // blank line between headers and content, very important !
if (method.equals("GET")) { // GET method so we return content
dataOut.write(fileData, 0, fileLength);
dataOut.flush();
}
VerboseLogger.info("File " + fileRequested + " of type " + contentType + " returned");
}

View file

@ -11,12 +11,16 @@ public class ApiRequests {
return "[\"" + value + "\"]";
}
public static String playerLookupRequest(String playerName) {
return gson.toJson(ApiRequestsUtil.playerLookup(playerName));
}
public static String playerNamesRequest() {
return gson.toJsonTree(ApiRequestsUtil.convertPlayerList(ApiRequestsUtil.getSERVER_METADATA().players().get().sample())).getAsJsonArray().toString();
}
public static String playerInfoRequest(String playerName) {
return gson.toJson(ApiRequestsUtil.getPlayerInfo(playerName));
public static String playerInfoAllRequest(String playerUuid) {
return gson.toJson(ApiRequestsUtil.getPlayerInfo(playerUuid));
}
public static String serverMetadataRequest() {

View file

@ -124,9 +124,18 @@ public class ApiRequestsUtil {
return ApiRequestsUtil.getSERVER_METADATA().favicon().get().iconBytes();
}
public static JsonObject getPlayerInfo(String playerName) {
public static JsonObject playerLookup(String playerName) {
for (ServerPlayerEntity player : ApiRequestsUtil.getSERVER_PLAYER_ENTITY_LIST()) {
if (player.getName().getString().equals(playerName)) {
return JsonParser.parseString("{\"uuid\":\"" + player.getUuidAsString() + "\"}").getAsJsonObject();
}
}
return gson.toJsonTree(ErrorHandler.customError(404, "Player Not Found")).getAsJsonObject();
}
public static JsonObject getPlayerInfo(String uuid) {
for (ServerPlayerEntity player : ApiRequestsUtil.getSERVER_PLAYER_ENTITY_LIST()) {
if (player.getUuidAsString().equals(uuid)) {
try {
String sleepDirection = (player.getSleepingDirection() != null) ? player.getSleepingDirection().asString() : null;
Vec<Integer> sleepPosition = new Vec<>();

View file

@ -13,6 +13,14 @@ public class ErrorHandler {
return gson.toJsonTree(badRequest()).getAsJsonObject().toString();
}
public static Error unauthorized() {
return new Error(401, "Unauthorized");
}
public static String unauthorizedString() {
return gson.toJsonTree(unauthorized()).getAsJsonObject().toString();
}
public static Error internalServerError() {
return new Error(500, "Internal Server Error");
}

View file

@ -70,11 +70,22 @@ public class ApiV2Handler {
if (isTokenValid) {
request = request.replace("/api/v2/", "");
if (request.startsWith("playerinfo?playername=")) {
String playerName = request.replace("playerinfo?playername=", "");
return ApiRequests.playerInfoRequest(playerName);
} else if (request.startsWith("playerinfo?playeruuid=")) {
return ErrorHandler.badRequestString();
if (request.startsWith("playerlookup?uuid=")) {
return ApiRequests.playerLookupRequest(request.replace("playerlookup?uuid=", ""));
} else if (request.startsWith("playerinfo/inventory?uuid=")) {
String playerName = request.replace("playerinfo/inventory?uuid=", "");
//return ApiRequests.playerInfoRequest(playerName);
return ErrorHandler.internalServerErrorString();
} else if (request.startsWith("playerinfo/inventory?playeruuid=")) {
//return ApiRequests.playerInfoRequestFromUuid(request.replace("playerinfo/inventory?playeruuid=", ""));
return ErrorHandler.internalServerErrorString();
} else if (request.startsWith("playerinfo/enderchest?playername=")) {
String playerName = request.replace("playerinfo/enderchest?playername=", "");
//return ApiRequests.playerInfoRequest(playerName);
return ErrorHandler.internalServerErrorString();
} else if (request.startsWith("playerinfo/enderchest?playeruuid=")) {
//return ApiRequests.playerInfoRequestFromUuid(request.replace("playerinfo/enderchest?playeruuid=", ""));
return ErrorHandler.internalServerErrorString();
} else {
return ErrorHandler.notFoundErrorString();
}