ServerAPI/src/main/java/xyz/etztech/serverapi/web/Web.java

188 lines
6.0 KiB
Java

package xyz.etztech.serverapi.web;
import io.javalin.Javalin;
import io.javalin.core.event.EventListener;
import io.javalin.core.plugin.Plugin;
import io.javalin.core.util.RouteOverviewPlugin;
import io.javalin.http.BadRequestResponse;
import io.javalin.http.Context;
import io.javalin.http.UnauthorizedResponse;
import io.javalin.plugin.graphql.GraphQLOptions;
import io.javalin.plugin.graphql.GraphQLPlugin;
import io.javalin.plugin.json.JavalinJson;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.helpers.NOPLogger;
import xyz.etztech.serverapi.ServerAPI;
import xyz.etztech.serverapi.token.TokenList;
import xyz.etztech.serverapi.web.api.BanAPI;
import xyz.etztech.serverapi.web.api.BroadcastAPI;
import xyz.etztech.serverapi.web.api.CustomAPI;
import xyz.etztech.serverapi.web.api.ErrorAPI;
import java.util.List;
public class Web {
private final IProvider provider;
private TokenList tokens;
private List<String> custom;
private Javalin app;
public Web(IProvider provider) {
this.provider = provider;
}
public void start(int port, TokenList tokens, List<String> custom) {
Javalin.log = NOPLogger.NOP_LOGGER;
app = Javalin.create(config -> {
config.registerPlugin(graphql());
config.registerPlugin(new RouteOverviewPlugin("/"));
config.enableCorsForAllOrigins();
}).events(this::events).exception(Exception.class, this::exception);
this.custom = custom;
this.tokens = tokens;
if (tokens != null) {
provider.log(String.format("Loaded %d tokens", tokens.getTokens().size()));
if (!tokens.isProtectPOST()) {
provider.log("WARNING: You have disabled POST protection, which can enable users to arbitrarily run actions against your server.");
}
app.before(this::access);
}
// REST Endpoints
// For GraphQL endpoints, see GraphQL.java
app.get("/bans", this::bans);
app.get("/players", this::players);
app.get("/plugins", this::plugins);
app.get("/ping", this::ping);
app.get("/tps", this::tps);
app.get("/worlds", this::worlds);
app.get("/worlds/:name", this::world);
app.get("/chat", this::chat);
app.post("/kick", this::kick);
app.post("/ban", this::ban);
app.post("/unban", this::unban);
app.post("/broadcast", this::broadcast);
app.post("/custom", this::custom);
// What in the actual fuck... https://github.com/tipsy/javalin/issues/358
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(ServerAPI.class.getClassLoader());
app.start(port);
Thread.currentThread().setContextClassLoader(classLoader);
}
public void stop() {
if (app != null) {
app.stop();
}
}
public void events(EventListener event) {
event.serverStarting(() -> provider.log("Starting API..."));
event.serverStarted(() -> provider.log("API is ready!"));
event.serverStartFailed(() -> provider.log("Could not start API..."));
event.serverStopping(() -> provider.log("Stopping API..."));
event.serverStopped(() -> provider.log("API is stopped!"));
}
public void exception(Exception exception, Context ctx) {
provider.log(String.format("API Exception at %s:\n%s",
ctx.req.getRequestURI(),
ExceptionUtils.getStackTrace(exception)));
ctx.status(500);
ctx.json(new ErrorAPI(500, "Internal Error"));
}
public void access(Context ctx) {
String token = ctx.header("X-ServerAPI-Token");
if (token == null) {
token = ctx.queryParam("token");
}
boolean isGET = "GET".equalsIgnoreCase(ctx.method());
boolean isPOST = "POST".equalsIgnoreCase(ctx.method());
if (isGET && !tokens.isProtectGET()) return;
if (isPOST && !tokens.isProtectPOST()) return;
String finalToken = token;
boolean pass = tokens.getTokens().stream().anyMatch(t -> t.getToken().equals(finalToken) &&
((isGET && t.canGET()) || (isPOST && t.canPOST())));
if (!pass) {
throw new UnauthorizedResponse();
}
}
public Plugin graphql() {
GraphQLOptions options = new GraphQLOptions("/graphql", null)
.addPackage("xyz.etztech.serverapi.web.api")
.register(new GraphQL(provider));
return new GraphQLPlugin(options);
}
public void tps(Context ctx) {
ctx.json(provider.TPS());
}
public void worlds(Context ctx) {
ctx.json(provider.worlds());
}
public void world(Context ctx) {
ctx.json(provider.world(ctx.pathParam("name")));
}
public void chat(Context ctx) {
ctx.json(provider.chat());
}
public void players(Context ctx) {
ctx.json(provider.players());
}
public void plugins(Context ctx) {
ctx.json(provider.plugins());
}
public void bans(Context ctx) {
ctx.json(provider.bans());
}
public void ping(Context ctx) {
ctx.json(provider.ping());
}
public void kick(Context ctx) {
provider.kick(JavalinJson.fromJson(ctx.body(), BanAPI.class));
ctx.status(200);
}
public void ban(Context ctx) {
provider.ban(JavalinJson.fromJson(ctx.body(), BanAPI.class));
ctx.status(200);
}
public void unban(Context ctx) {
provider.unban(JavalinJson.fromJson(ctx.body(), BanAPI.class));
ctx.status(200);
}
public void broadcast(Context ctx) {
provider.broadcast(JavalinJson.fromJson(ctx.body(), BroadcastAPI.class));
ctx.status(200);
}
public void custom(Context ctx) {
CustomAPI capi = JavalinJson.fromJson(ctx.body(), CustomAPI.class);
if (custom.stream().noneMatch(c -> c.equalsIgnoreCase(capi.getCommand()))) {
throw new BadRequestResponse();
}
provider.custom(capi);
ctx.status(200);
}
}