mirror of
https://github.com/JonasunderscoreJones/McWebserver.git
synced 2025-10-22 19:09:19 +02:00
Implement Basic Auth for Webserver
This commit is contained in:
parent
aa51d85388
commit
54188d7c31
6 changed files with 106 additions and 36 deletions
|
@ -15,6 +15,7 @@ public class ModConfigs {
|
||||||
public static String WEB_ROOT;
|
public static String WEB_ROOT;
|
||||||
public static String WEB_FILE_ROOT;
|
public static String WEB_FILE_ROOT;
|
||||||
public static String WEB_FILE_404;
|
public static String WEB_FILE_404;
|
||||||
|
public static Boolean WEB_REQUIRE_TOKEN;
|
||||||
public static Boolean SERVER_API_ENABLED;
|
public static Boolean SERVER_API_ENABLED;
|
||||||
public static Boolean ADV_API_ENABLED;
|
public static Boolean ADV_API_ENABLED;
|
||||||
public static Boolean API_INGAME_COMMAND_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.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.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.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", 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.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");
|
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_ROOT = CONFIG.getOrDefault("web.root", "webserver/");
|
||||||
WEB_FILE_ROOT = CONFIG.getOrDefault("web.file.root", "index.html");
|
WEB_FILE_ROOT = CONFIG.getOrDefault("web.file.root", "index.html");
|
||||||
WEB_FILE_404 = CONFIG.getOrDefault("web.file.404", "404.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);
|
SERVER_API_ENABLED = CONFIG.getOrDefault("web.api", true);
|
||||||
ADV_API_ENABLED = CONFIG.getOrDefault("web.api.adv", true);
|
ADV_API_ENABLED = CONFIG.getOrDefault("web.api.adv", true);
|
||||||
API_INGAME_COMMAND_ENABLED = CONFIG.getOrDefault("web.api.cmd", true);
|
API_INGAME_COMMAND_ENABLED = CONFIG.getOrDefault("web.api.cmd", true);
|
||||||
|
|
|
@ -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.ApiRequests;
|
||||||
import me.jonasjones.mcwebserver.web.api.ApiRequestsUtil;
|
import me.jonasjones.mcwebserver.web.api.ApiRequestsUtil;
|
||||||
import me.jonasjones.mcwebserver.web.api.v2.ApiV2Handler;
|
import me.jonasjones.mcwebserver.web.api.v2.ApiV2Handler;
|
||||||
|
import me.jonasjones.mcwebserver.web.api.v2.tokenmgr.TokenManager;
|
||||||
|
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
@ -22,6 +23,7 @@ import java.nio.file.Files;
|
||||||
import java.nio.file.LinkOption;
|
import java.nio.file.LinkOption;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
@ -121,8 +123,8 @@ public class HttpServer implements Runnable {
|
||||||
while ((header = in.readLine()) != null && !header.isEmpty()) {
|
while ((header = in.readLine()) != null && !header.isEmpty()) {
|
||||||
|
|
||||||
// Check if the header contains your API token
|
// Check if the header contains your API token
|
||||||
if (header.startsWith("Authorization: Bearer ")) {
|
if (header.startsWith("Authorization: Basic ")) {
|
||||||
apiToken = header.substring("Authorization: Bearer ".length());
|
apiToken = header.substring("Authorization: Basic ".length());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,35 +240,68 @@ public class HttpServer implements Runnable {
|
||||||
fileRequested = fileRequested.substring(1);
|
fileRequested = fileRequested.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Path file = WEB_ROOT.resolve(fileRequested).toRealPath(LinkOption.NOFOLLOW_LINKS);
|
try {
|
||||||
if (!file.startsWith(WEB_ROOT)) {
|
boolean isAuthorized = false;
|
||||||
VerboseLogger.warn("Access to file outside root: " + file);
|
if (!ModConfigs.WEB_REQUIRE_TOKEN) {
|
||||||
throw new NoSuchFileException(fileRequested);
|
isAuthorized = true;
|
||||||
}
|
} else if (apiToken != null) {
|
||||||
int fileLength = (int) Files.size(file);
|
isAuthorized = TokenManager.isTokenValid(apiToken);
|
||||||
int fileExtensionStartIndex = fileRequested.lastIndexOf(".") + 1;
|
}
|
||||||
String contentType;
|
|
||||||
if (fileExtensionStartIndex > 0) {
|
if (isAuthorized) {
|
||||||
contentType = mimetypeidentifier.compare(fileRequested.substring(fileExtensionStartIndex));
|
|
||||||
} else {
|
Path file = WEB_ROOT.resolve(fileRequested).toRealPath(LinkOption.NOFOLLOW_LINKS);
|
||||||
contentType = "text/plain";
|
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");
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,12 +11,16 @@ public class ApiRequests {
|
||||||
return "[\"" + value + "\"]";
|
return "[\"" + value + "\"]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String playerLookupRequest(String playerName) {
|
||||||
|
return gson.toJson(ApiRequestsUtil.playerLookup(playerName));
|
||||||
|
}
|
||||||
|
|
||||||
public static String playerNamesRequest() {
|
public static String playerNamesRequest() {
|
||||||
return gson.toJsonTree(ApiRequestsUtil.convertPlayerList(ApiRequestsUtil.getSERVER_METADATA().players().get().sample())).getAsJsonArray().toString();
|
return gson.toJsonTree(ApiRequestsUtil.convertPlayerList(ApiRequestsUtil.getSERVER_METADATA().players().get().sample())).getAsJsonArray().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String playerInfoRequest(String playerName) {
|
public static String playerInfoAllRequest(String playerUuid) {
|
||||||
return gson.toJson(ApiRequestsUtil.getPlayerInfo(playerName));
|
return gson.toJson(ApiRequestsUtil.getPlayerInfo(playerUuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String serverMetadataRequest() {
|
public static String serverMetadataRequest() {
|
||||||
|
|
|
@ -124,9 +124,18 @@ public class ApiRequestsUtil {
|
||||||
return ApiRequestsUtil.getSERVER_METADATA().favicon().get().iconBytes();
|
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()) {
|
for (ServerPlayerEntity player : ApiRequestsUtil.getSERVER_PLAYER_ENTITY_LIST()) {
|
||||||
if (player.getName().getString().equals(playerName)) {
|
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 {
|
try {
|
||||||
String sleepDirection = (player.getSleepingDirection() != null) ? player.getSleepingDirection().asString() : null;
|
String sleepDirection = (player.getSleepingDirection() != null) ? player.getSleepingDirection().asString() : null;
|
||||||
Vec<Integer> sleepPosition = new Vec<>();
|
Vec<Integer> sleepPosition = new Vec<>();
|
||||||
|
|
|
@ -13,6 +13,14 @@ public class ErrorHandler {
|
||||||
return gson.toJsonTree(badRequest()).getAsJsonObject().toString();
|
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() {
|
public static Error internalServerError() {
|
||||||
return new Error(500, "Internal Server Error");
|
return new Error(500, "Internal Server Error");
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,11 +70,22 @@ public class ApiV2Handler {
|
||||||
|
|
||||||
if (isTokenValid) {
|
if (isTokenValid) {
|
||||||
request = request.replace("/api/v2/", "");
|
request = request.replace("/api/v2/", "");
|
||||||
if (request.startsWith("playerinfo?playername=")) {
|
if (request.startsWith("playerlookup?uuid=")) {
|
||||||
String playerName = request.replace("playerinfo?playername=", "");
|
return ApiRequests.playerLookupRequest(request.replace("playerlookup?uuid=", ""));
|
||||||
return ApiRequests.playerInfoRequest(playerName);
|
} else if (request.startsWith("playerinfo/inventory?uuid=")) {
|
||||||
} else if (request.startsWith("playerinfo?playeruuid=")) {
|
String playerName = request.replace("playerinfo/inventory?uuid=", "");
|
||||||
return ErrorHandler.badRequestString();
|
//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 {
|
} else {
|
||||||
return ErrorHandler.notFoundErrorString();
|
return ErrorHandler.notFoundErrorString();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue