2020-08-07 19:30:36 +00:00
|
|
|
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;
|
2020-10-06 23:20:02 +00:00
|
|
|
import io.javalin.http.BadRequestResponse;
|
2020-08-07 19:30:36 +00:00
|
|
|
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;
|
2020-10-06 23:20:02 +00:00
|
|
|
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;
|
2020-08-07 19:30:36 +00:00
|
|
|
import xyz.etztech.serverapi.web.api.ErrorAPI;
|
|
|
|
|
2020-10-06 23:20:02 +00:00
|
|
|
import java.util.List;
|
|
|
|
|
2020-08-07 19:30:36 +00:00
|
|
|
public class Web {
|
|
|
|
private final IProvider provider;
|
2020-10-06 23:20:02 +00:00
|
|
|
private TokenList tokens;
|
|
|
|
private List<String> custom;
|
2020-08-07 19:30:36 +00:00
|
|
|
private Javalin app;
|
|
|
|
|
|
|
|
public Web(IProvider provider) {
|
|
|
|
this.provider = provider;
|
|
|
|
}
|
|
|
|
|
2020-10-06 23:20:02 +00:00
|
|
|
public void start(int port, TokenList tokens, List<String> custom) {
|
2020-08-07 19:30:36 +00:00
|
|
|
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);
|
|
|
|
|
2020-10-06 23:20:02 +00:00
|
|
|
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.");
|
|
|
|
}
|
2020-08-07 19:30:36 +00:00
|
|
|
app.before(this::access);
|
|
|
|
}
|
|
|
|
|
2020-08-12 00:43:42 +00:00
|
|
|
// REST Endpoints
|
|
|
|
// For GraphQL endpoints, see GraphQL.java
|
2020-08-07 19:30:36 +00:00
|
|
|
app.get("/bans", this::bans);
|
|
|
|
app.get("/players", this::players);
|
2020-08-12 00:43:42 +00:00
|
|
|
app.get("/plugins", this::plugins);
|
|
|
|
app.get("/ping", this::ping);
|
2020-08-07 19:30:36 +00:00
|
|
|
app.get("/tps", this::tps);
|
|
|
|
app.get("/worlds", this::worlds);
|
|
|
|
app.get("/worlds/:name", this::world);
|
2020-11-09 03:55:43 +00:00
|
|
|
app.get("/chat", this::chat);
|
2020-08-07 19:30:36 +00:00
|
|
|
|
2020-10-06 23:20:02 +00:00
|
|
|
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);
|
|
|
|
|
2020-08-07 19:30:36 +00:00
|
|
|
// 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) {
|
2020-10-06 23:20:02 +00:00
|
|
|
String token = ctx.header("X-ServerAPI-Token");
|
|
|
|
if (token == null) {
|
|
|
|
token = ctx.queryParam("token");
|
2020-08-07 19:30:36 +00:00
|
|
|
}
|
2020-10-06 23:20:02 +00:00
|
|
|
boolean isGET = "GET".equalsIgnoreCase(ctx.method());
|
|
|
|
boolean isPOST = "POST".equalsIgnoreCase(ctx.method());
|
|
|
|
|
|
|
|
if (isGET && !tokens.isProtectGET()) return;
|
|
|
|
if (isPOST && !tokens.isProtectPOST()) return;
|
2020-08-07 19:30:36 +00:00
|
|
|
|
2020-10-06 23:20:02 +00:00
|
|
|
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();
|
2020-08-07 19:30:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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")));
|
|
|
|
}
|
|
|
|
|
2020-11-09 03:55:43 +00:00
|
|
|
public void chat(Context ctx) {
|
|
|
|
ctx.json(provider.chat());
|
|
|
|
}
|
|
|
|
|
2020-08-07 19:30:36 +00:00
|
|
|
public void players(Context ctx) {
|
|
|
|
ctx.json(provider.players());
|
|
|
|
}
|
|
|
|
|
2020-08-12 00:43:42 +00:00
|
|
|
public void plugins(Context ctx) {
|
|
|
|
ctx.json(provider.plugins());
|
|
|
|
}
|
|
|
|
|
2020-08-07 19:30:36 +00:00
|
|
|
public void bans(Context ctx) {
|
|
|
|
ctx.json(provider.bans());
|
|
|
|
}
|
|
|
|
|
2020-08-12 00:43:42 +00:00
|
|
|
public void ping(Context ctx) {
|
|
|
|
ctx.json(provider.ping());
|
2020-08-07 19:30:36 +00:00
|
|
|
}
|
2020-10-06 23:20:02 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2020-08-07 19:30:36 +00:00
|
|
|
}
|