This is a base plugin for the game Hytale, allowing other Hytale plugins to serve web-based content.
Many plugin use cases require serving data via HTTP, such as for webhooks, live maps, or for exposing data about the connected players. However, multiple plugins each opening their own HTTP servers for their respective use cases is unnecessary overhead, and causes headaches for server admins and service providers.
The aim of this plugin is to provide a common solution for plugin developers, and to solve typical requirements around web servers in one single place, so that plugins stay compatible with each other.
Game Server Providers are encouraged to detect the presence of this plugin, and to provide it with a configuration that ensures compatibility with their respective hosting platform.
- Secure by default: TLS using self-signed or user-provided certificates, as well as support for certificate retrieval.
- Player Authentication: Players may authenticate based on their Hytale account, allowing plugins to act in the context of that respective player.
- Permission checks: Built-in support for performing authorization checks based on an authenticated player's permissions on the Hytale server.
- Service Accounts (API Users): Allowing server owners to create accounts independent of a player account, which can still be managed using Hytale's permission system.
- Extensibility: All important implementations are behind interfaces, so that they can be replaced
if required. To ensure intercompatibility with other web servers, classes from
jakarta.servlet.httpare used.
Copy the plugin JAR file into your server's mods/ folder.
By default, the web server binds to the game server's port +3. This can be overridden by creating
a file under mods/Nitrado_WebServer/config.json:
{
"BindHost": "127.0.0.1",
"BindPort": 7003
}TLS is enabled by default using a self-signed certificate. To customize TLS settings, add a Tls section to your config:
{
"BindHost": "127.0.0.1",
"BindPort": 7003,
"Tls": {
"Insecure": false,
"CertificateProvider": "selfsigned"
}
}Disable TLS (not recommended):
{
"Tls": {
"Insecure": true
}
}Certificate Providers:
| Provider | Description |
|---|---|
selfsigned |
Generates a self-signed certificate (default) |
pem |
Uses PEM certificate and key files |
letsencrypt |
Obtains certificates from Let's Encrypt via ACME |
Self-signed configuration:
{
"Tls": {
"CertificateProvider": "selfsigned",
"SelfSigned": {
"CommonName": "my-server.example.com"
}
}
}PEM configuration:
{
"Tls": {
"CertificateProvider": "pem",
"Pem": {
"CertificatePath": "/path/to/certificate.pem",
"PrivateKeyPath": "/path/to/private-key.pem"
}
}
}Let's Encrypt configuration:
{
"Tls": {
"CertificateProvider": "letsencrypt",
"LetsEncrypt": {
"Domain": "my-server.example.com",
"Production": false
}
}
}Set Production to true to use Let's Encrypt's production servers (has rate limits). When false, uses the staging environment for testing.
Example using maven:
<dependencies>
...
<dependency>
<groupId>net.nitrado.hytale.plugins</groupId>
<artifactId>webserver</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
...
</dependencies>[[ TODO: Example for Gradle ]]
The <scope>provided</scope> indicates that your resulting plugin does not need to bundle the web
server code, as it is provided by the Hytale server at runtime.
In your plugin's manifest.json, define Nitrado:WebServer as a dependency:
{
"Dependencies": {
"Nitrado:WebServer": "*"
}
}Below is example code taken from Nitrado:Query:
public class Query extends JavaPlugin {
private WebServerPlugin webServerPlugin;
public Query(@Nonnull JavaPluginInit init) {
super(init);
}
@Override
protected void setup() {
this.registerHandlers();
}
private void registerHandlers() {
var plugin = PluginManager.get().getPlugin(new PluginIdentifier("Nitrado", "WebServer"));
if (!(plugin instanceof WebServerPlugin webServer)) {
return;
}
this.webServerPlugin = webServer;
try {
webServerPlugin.addServlet(this, "", new QueryServlet());
} catch (Exception e) {
getLogger().at(Level.SEVERE).withCause(e).log("Failed to register route.");
}
}
@Override
protected void shutdown() {
webServerPlugin.removeServlets(this);
}
}The handler will be automatically registered at /<PluginGroup>/<PluginName>, so /Nitrado/Query for
the example above. This approach avoids collisions between multiple plugins.
Also note that in the shutdown() method the plugin removes itself from the web server again. This ensures
that you can reload your plugin at runtime.
To check for permissions, the most convenient way is via annotations in the servlet.
In the example below, the doGet handler requires the requesting user to have at least one of three given permissions.
If none of these permissions is fulfilled, the request is declined. The registered servlet can then still check for
those permissions to adjust its output:
public class QueryServlet extends HttpServlet {
@Override
@RequirePermissions(value = {Permissions.READ_PLAYERS, Permissions.READ_SERVER, Permissions.READ_UNIVERSE}, mode = RequirePermissions.Mode.ANY)
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setContentType("application/json");
Document doc = new Document();
var principal = req.getUserPrincipal();
if (principal instanceof HytaleUserPrincipal user) {
if (user.hasPermission(Permissions.READ_SERVER)) {
this.addServerData(doc);
}
if (user.hasPermission(Permissions.READ_PLAYERS)) {
this.addPlayerData(doc);
}
if (user.hasPermission(Permissions.READ_UNIVERSE)) {
this.addUniverseData(doc);
}
}
resp.getWriter().println(doc.toJson(JsonWriterSettings.builder().indent(true).build()));
}
// ...
}If you cannot use the @RequirePermissions annotation (e.g., when using a third-party servlet, a dynamically
generated servlet, or when you need to configure permissions at runtime), you can use RequirePermissionsFilter
instead.
The filter is registered alongside your servlet and performs the same permission checks:
// Require ALL permissions (default behavior)
webServerPlugin.addServlet(this, "/protected", new ThirdPartyServlet(),
new RequirePermissionsFilter("my.plugin.web.read", "my.plugin.web.write"));
// Require ANY of the permissions (pass `true` as first argument)
webServerPlugin.addServlet(this, "/protected", new ThirdPartyServlet(),
new RequirePermissionsFilter(true, "my.plugin.web.read.a", "my.plugin.web.read.b"));The filter behaves identically to the annotation:
- Returns
401 Unauthorizedif no user is authenticated (or if the anonymous user lacks permission) - Returns
403 Forbiddenif an authenticated user lacks the required permissions
Note: When using both the annotation and the filter on the same servlet, both checks must pass.
The WebServer plugin provides the following built-in permissions:
| Permission | Description |
|---|---|
nitrado.webserver.command.logincode.create |
Allows a player to create a login code via the /webserver code create command. |
nitrado.webserver.web.list.plugins |
Allows viewing the list of installed plugins through the web UI. |
A player with the nitrado.webserver.command.logincode.create permission can execute the following command in-game:
/webserver code create
This displays a short-lived code that can be used to log in via the web server. Users can also use this code to assign a long-lived password so that they can continue to log in even while not connected in-game.
OAuth support will be added if/when this functionality is officially supported by Hytale.
Service Accounts are intended for processes that automatically interact with the server through HTTP APIs. For security purposes, it is recommended to use service accounts that have the exact set of permissions to fulfill the tasks they are intended for.
Service accounts will be automatically added to the SERVICE_ACCOUNT group to make them easier to identify in
permission management.
Service Accounts can be either created through the Web UI (not implemented yet) or provisioned automatically.
You can then use a Service Account password to authenticate against the web server using Basic Auth, such as with:
curl -u serviceaccount.MyServiceAccountName:MyPassword <url>
Note the serviceaccount. prefix when authenticating with a service account.
[[ TODO ]]
Create the folder mods/Nitrado_WebServer/provisioning. In it, you can place files that end in
.serviceaccount.json, such as example.serviceaccount.json with the following content structure:
{
"Enabled": true,
"Name": "serviceaccount.example",
"PasswordHash": "$2b$10$ME8G6/YZ3hXUOAhLs3mrh.a3cuZTvzE2zGjQIqxztgPXKtm7sFCde",
"Groups": ["Creative"],
"Permissions": ["nitrado.query.web.read.players"]
}A service account with Enabled set to true will be automatically created or updated on server start. Setting
Enabled to false will lead to the service account to be removed, also removing it from any groups and permissions,
to not clutter your permission management.
This plugin automatically creates a permissions entry for a user with the UUID 00000000-0000-0000-0000-000000000000 in
group ANONYMOUS. Un-authenticated requests will appear as that user, with the permissions that have been assigned to
that user.
With this mechanism, plugin developers can set up permissions for all actions provided by their plugins, but still leave it up to server admins to decide which of those should be available to the public.
Please note: While failed permission checks for an authenticated user result in a 403 Forbidden, failed permission
checks for the anonymous user result in a 401 Unauthorized, which may then trigger an authentication flow.
Community contributions are welcome and encouraged. If you are a plugin developer and this plugin does not fulfill your needs, please consider contributing to this repository before building your own web server implementation.
Due to the nature of this plugin, we need to ensure that it is versatile enough to fulfill the needs of plugin developers, but we also need to avoid the plugin to become bloated with features that would make it cumbersome to use. So if you plan to work on a feature, please open an Issue here on GitHub first.
If you believe to have found a security vulnerability, please report your findings via security@nitrado.net.