Skip to content

Commit

Permalink
Implement Basic Auth for Webserver
Browse files Browse the repository at this point in the history
  • Loading branch information
JonasunderscoreJones committed Mar 2, 2024
1 parent aa51d85 commit 54188d7
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -45,6 +46,7 @@ private static void createConfigs() {
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");
Expand All @@ -59,6 +61,7 @@ private static void assignConfigs() {
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);
Expand Down
89 changes: 62 additions & 27 deletions src/main/java/me/jonasjones/mcwebserver/web/HttpServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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;
Expand All @@ -22,6 +23,7 @@
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;

Expand Down Expand Up @@ -121,8 +123,8 @@ public void run() {
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());

This comment has been minimized.

Copy link
@danya02

danya02 Mar 5, 2024

You just broke any clients that already use your server because their tokens will not work anymore even though they're correct.

Also, Basic Auth is not like a token: what is sent there is the base64-encoded username and password, so you can't generate some random bytes (which is what tokens are) and call that a Basic Auth string.

This comment has been minimized.

Copy link
@JonasunderscoreJones

JonasunderscoreJones Mar 5, 2024

Author Owner

That's fair yeah, I'll change that back.

}
}

Expand Down Expand Up @@ -238,35 +240,68 @@ public void run() {
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);
}

byte[] fileData = readFileData(file);
if (isAuthorized) {

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

VerboseLogger.info("File " + fileRequested + " of type " + contentType + " returned");


}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ public static String singleValueRequest(String value) {
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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,18 @@ public static byte[] getServerIcon() {
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<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ public static String badRequestString() {
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");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,22 @@ public static String handle(String request, String token) throws NoSuchAlgorithm

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

0 comments on commit 54188d7

Please sign in to comment.