Skip to content

Commit e856366

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents 01db848 + c11bf65 commit e856366

File tree

8 files changed

+320
-3
lines changed

8 files changed

+320
-3
lines changed

.github/workflows/build.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,13 @@ jobs:
1818
check-latest: true
1919
- name: Build with Gradle
2020
run: ./gradlew build
21-
- name: Upload Artifacts to GitHub
21+
- name: Upload ViaProxy plugin to GitHub
2222
uses: actions/upload-artifact@v4
2323
with:
24-
name: Artifacts
24+
name: AuthHook ViaProxy
2525
path: build/libs/
26+
- name: Upload AuthHook agent to GitHub
27+
uses: actions/upload-artifact@v4
28+
with:
29+
name: AuthHook server agent
30+
path: Agent/build/libs/

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,28 @@
11
# ViaProxyAuthHook
2+
Minecraft Server modification to allow [ViaProxy](https://github.com/RaphiMC/ViaProxy) clients to join online mode servers.
3+
4+
## How it works
5+
This plugin works by redirecting the authentication requests from the server to the ViaProxy instance.
6+
ViaProxy then checks if the client is authenticated with ViaProxy and sends the result back to the server.
7+
Clients which are not authenticated with ViaProxy will be authenticated with the official Mojang authentication servers.
8+
9+
The modification has been confirmed to work on Vanilla, Spigot, Paper, Fabric, Forge and BungeeCord.
10+
11+
## Installation
12+
1. Download the latest version from [GitHub Actions](https://github.com/ViaVersionAddons/ViaProxyAuthHook/actions).
13+
2. Put the jar file into the plugins folder of ViaProxy
14+
3. Run ViaProxy once to generate the config file
15+
4. Make sure to enable "Proxy Online Mode" in the ViaProxy CLI or config file
16+
5. Copy the secret key from the AuthHook config file (You will need it later for the server)
17+
6. Download the latest version of the AuthHook agent (Same link as step 1)
18+
7. Put the AuthHook agent into the same folder as the server jar
19+
8. Add the following JVM argument to the server start command: `-javaagent:ViaProxyAuthHook-x.x.x.jar` (Replace x.x.x with the version of the AuthHook agent you downloaded)
20+
9. Start the server once to generate the config file
21+
10. Open the config file (It's in the same folder as the server jar) and set the secret key to the key you copied in step 4
22+
11. Start both the server and ViaProxy. You can now switch the authentication mode to AuthHook (Use `AUTH_HOOK` for CLI or config file).
23+
24+
## Contact
25+
If you encounter any issues, please report them on the
26+
[issue tracker](https://github.com/ViaVersionAddons/ViaProxyAuthHook/issues).
27+
If you just want to talk or need help using ViaProxyAuthHook feel free to join my
28+
[Discord](https://discord.gg/dCzT9XHEWu).

gradlew

100644100755
File mode changed.

src/main/java/net/raphimc/authhook/AuthHook.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,58 @@
1717
*/
1818
package net.raphimc.authhook;
1919

20+
import net.lenni0451.lambdaevents.EventHandler;
21+
import net.lenni0451.reflect.Enums;
22+
import net.lenni0451.reflect.stream.RStream;
23+
import net.raphimc.authhook.config.AuthHookConfig;
2024
import net.raphimc.viaproxy.ViaProxy;
2125
import net.raphimc.viaproxy.plugins.ViaProxyPlugin;
26+
import net.raphimc.viaproxy.plugins.events.JoinServerRequestEvent;
27+
import net.raphimc.viaproxy.plugins.events.ViaProxyLoadedEvent;
28+
import net.raphimc.viaproxy.protocoltranslator.viaproxy.ViaProxyConfig;
29+
import net.raphimc.viaproxy.ui.I18n;
30+
import net.raphimc.viaproxy.util.logging.Logger;
31+
32+
import java.net.InetSocketAddress;
33+
import java.util.Map;
34+
import java.util.Properties;
2235

2336
public class AuthHook extends ViaProxyPlugin {
2437

38+
private static ViaProxyConfig.AuthMethod AUTH_HOOK;
39+
private AuthHookHttpServer authHookHttpServer;
40+
2541
@Override
2642
public void onEnable() {
2743
ViaProxy.EVENT_MANAGER.register(this);
44+
AuthHookConfig.load(this.getDataFolder());
45+
46+
this.authHookHttpServer = new AuthHookHttpServer((InetSocketAddress) AuthHookConfig.bindAddress);
47+
Logger.LOGGER.info("AuthHook is listening on http://" + AuthHookConfig.bindAddress);
48+
49+
AUTH_HOOK = Enums.newInstance(ViaProxyConfig.AuthMethod.class, "AUTH_HOOK", ViaProxyConfig.AuthMethod.values().length, new Class[]{String.class}, new Object[]{"authhook.auth_method.name"});
50+
Enums.addEnumInstance(ViaProxyConfig.AuthMethod.class, AUTH_HOOK);
51+
}
52+
53+
@EventHandler
54+
private void onViaProxyLoaded(ViaProxyLoadedEvent event) {
55+
if (!ViaProxy.getConfig().isProxyOnlineMode()) {
56+
Logger.LOGGER.error("Proxy online mode is disabled, please enable it to use the AuthHook plugin!");
57+
Logger.LOGGER.error("Without online mode the AuthHook plugin would be effectively useless");
58+
Logger.LOGGER.error("Shutting down...");
59+
System.exit(0);
60+
}
61+
62+
final Map<String, Properties> locales = RStream.of(I18n.class).fields().by("LOCALES").get();
63+
locales.get("en_US").setProperty(AUTH_HOOK.getGuiTranslationKey(), "Use AuthHook");
64+
}
65+
66+
@EventHandler
67+
private void onJoinServerRequest(JoinServerRequestEvent event) {
68+
if (ViaProxy.getConfig().getAuthMethod() == AUTH_HOOK) {
69+
this.authHookHttpServer.addPendingConnection(event.getServerIdHash(), event.getProxyConnection());
70+
event.setCancelled(true);
71+
}
2872
}
2973

3074
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* This file is part of ViaProxyAuthHook - https://github.com/ViaVersionAddons/ViaProxyAuthHook
3+
* Copyright (C) 2024-2024 RK_01/RaphiMC and contributors
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package net.raphimc.authhook;
19+
20+
import com.google.common.cache.CacheBuilder;
21+
import com.google.gson.JsonArray;
22+
import com.google.gson.JsonObject;
23+
import com.mojang.authlib.GameProfile;
24+
import io.netty.bootstrap.ServerBootstrap;
25+
import io.netty.channel.*;
26+
import io.netty.channel.nio.NioEventLoopGroup;
27+
import io.netty.channel.socket.nio.NioServerSocketChannel;
28+
import io.netty.handler.codec.http.*;
29+
import net.raphimc.authhook.config.AuthHookConfig;
30+
import net.raphimc.viaproxy.proxy.session.ProxyConnection;
31+
32+
import java.net.InetSocketAddress;
33+
import java.net.URI;
34+
import java.net.http.HttpClient;
35+
import java.util.List;
36+
import java.util.Map;
37+
import java.util.concurrent.TimeUnit;
38+
39+
public class AuthHookHttpServer {
40+
41+
private final InetSocketAddress bindAddress;
42+
private final ChannelFuture channelFuture;
43+
private final Map<String, ProxyConnection> pendingConnections = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).<String, ProxyConnection>build().asMap();
44+
45+
public AuthHookHttpServer(final InetSocketAddress bindAddress) {
46+
this.bindAddress = bindAddress;
47+
this.channelFuture = new ServerBootstrap()
48+
.group(new NioEventLoopGroup(0))
49+
.channel(NioServerSocketChannel.class)
50+
.option(ChannelOption.SO_BACKLOG, 128)
51+
.childOption(ChannelOption.TCP_NODELAY, true)
52+
.childOption(ChannelOption.SO_KEEPALIVE, true)
53+
.childHandler(new ChannelInitializer<>() {
54+
@Override
55+
protected void initChannel(Channel channel) {
56+
channel.pipeline().addLast("http_codec", new HttpServerCodec());
57+
channel.pipeline().addLast("http_handler", new SimpleChannelInboundHandler<>() {
58+
@Override
59+
protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
60+
if (!(msg instanceof HttpRequest request)) {
61+
return;
62+
}
63+
64+
if (request.uri().startsWith("/" + AuthHookConfig.secretKey + "/")) {
65+
final String uri = request.uri().substring(AuthHookConfig.secretKey.length() + 1);
66+
if (request.method().equals(HttpMethod.GET) && uri.startsWith("/session/minecraft/hasJoined")) {
67+
final QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.uri());
68+
if (queryStringDecoder.parameters().containsKey("username") && queryStringDecoder.parameters().containsKey("serverId")) {
69+
final String username = queryStringDecoder.parameters().get("username").get(0);
70+
final String serverId = queryStringDecoder.parameters().get("serverId").get(0);
71+
72+
final ProxyConnection proxyConnection = pendingConnections.remove(serverId + "_" + username);
73+
if (proxyConnection != null) {
74+
final GameProfile gameProfile = proxyConnection.getGameProfile();
75+
final JsonObject responseObj = new JsonObject();
76+
responseObj.addProperty("name", gameProfile.getName());
77+
responseObj.addProperty("id", gameProfile.getId().toString().replace("-", ""));
78+
if (!gameProfile.getProperties().isEmpty()) {
79+
final JsonArray propertiesArray = new JsonArray();
80+
gameProfile.getProperties().forEach((key, value) -> {
81+
final JsonObject propertyObj = new JsonObject();
82+
propertyObj.addProperty("name", key);
83+
propertyObj.addProperty("value", value.getValue());
84+
if (value.hasSignature()) {
85+
propertyObj.addProperty("signature", value.getSignature());
86+
}
87+
propertiesArray.add(propertyObj);
88+
});
89+
responseObj.add("properties", propertiesArray);
90+
}
91+
92+
final FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, ctx.alloc().buffer());
93+
response.content().writeBytes(responseObj.toString().getBytes());
94+
response.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
95+
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
96+
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
97+
ctx.writeAndFlush(response).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE).addListener(ChannelFutureListener.CLOSE);
98+
return;
99+
}
100+
}
101+
}
102+
103+
final HttpClient httpClient = HttpClient.newHttpClient();
104+
httpClient.sendAsync(java.net.http.HttpRequest.newBuilder().uri(URI.create("https://sessionserver.mojang.com" + uri)).build(), java.net.http.HttpResponse.BodyHandlers.ofByteArray())
105+
.thenAccept(response -> {
106+
final FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(response.statusCode()), ctx.alloc().buffer());
107+
fullHttpResponse.content().writeBytes(response.body());
108+
for (Map.Entry<String, List<String>> entry : response.headers().map().entrySet()) {
109+
if (!entry.getKey().startsWith(":")) {
110+
fullHttpResponse.headers().set(entry.getKey(), entry.getValue().get(0));
111+
}
112+
}
113+
fullHttpResponse.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
114+
ctx.writeAndFlush(fullHttpResponse).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE).addListener(ChannelFutureListener.CLOSE);
115+
});
116+
} else {
117+
ctx.close();
118+
}
119+
}
120+
121+
@Override
122+
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
123+
ctx.close();
124+
}
125+
});
126+
}
127+
})
128+
.bind(bindAddress)
129+
.syncUninterruptibly();
130+
}
131+
132+
public void addPendingConnection(final String serverIdHash, final ProxyConnection connection) {
133+
this.pendingConnections.put(serverIdHash + "_" + connection.getGameProfile().getName(), connection);
134+
}
135+
136+
public void stop() {
137+
if (this.channelFuture != null) {
138+
this.channelFuture.channel().close();
139+
}
140+
}
141+
142+
public Channel getChannel() {
143+
return this.channelFuture.channel();
144+
}
145+
146+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* This file is part of ViaProxyAuthHook - https://github.com/ViaVersionAddons/ViaProxyAuthHook
3+
* Copyright (C) 2024-2024 RK_01/RaphiMC and contributors
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package net.raphimc.authhook.config;
19+
20+
import net.lenni0451.optconfig.ConfigLoader;
21+
import net.lenni0451.optconfig.annotations.*;
22+
import net.lenni0451.optconfig.provider.ConfigProvider;
23+
import net.raphimc.viaproxy.util.AddressUtil;
24+
import net.raphimc.viaproxy.util.logging.Logger;
25+
26+
import java.io.File;
27+
import java.net.SocketAddress;
28+
import java.util.UUID;
29+
30+
@OptConfig
31+
public class AuthHookConfig {
32+
33+
@Option("secret-key")
34+
@Description("The secret key used to verify the servers. Paste this key into the auth_hook.properties config file on your server.")
35+
public static String secretKey = UUID.randomUUID().toString().replace("-", "");
36+
37+
@NotReloadable
38+
@Option("bind-address")
39+
@Description({"The address AuthHook should listen for HTTP requests."})
40+
@TypeSerializer(SocketAddressTypeSerializer.class)
41+
public static SocketAddress bindAddress = AddressUtil.parse("127.0.0.1:8080", null);
42+
43+
public static void load(final File dataFolder) {
44+
try {
45+
final ConfigLoader<AuthHookConfig> configLoader = new ConfigLoader<>(AuthHookConfig.class);
46+
configLoader.getConfigOptions().setResetInvalidOptions(true);
47+
configLoader.loadStatic(ConfigProvider.file(new File(dataFolder, "auth_hook.yml")));
48+
} catch (Throwable t) {
49+
Logger.LOGGER.error("Failed to load the AuthHook configuration!", t);
50+
System.exit(-1);
51+
}
52+
}
53+
54+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* This file is part of ViaProxyAuthHook - https://github.com/ViaVersionAddons/ViaProxyAuthHook
3+
* Copyright (C) 2024-2024 RK_01/RaphiMC and contributors
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package net.raphimc.authhook.config;
19+
20+
import net.lenni0451.optconfig.serializer.ConfigTypeSerializer;
21+
import net.raphimc.viaproxy.util.AddressUtil;
22+
23+
import java.net.SocketAddress;
24+
25+
public class SocketAddressTypeSerializer extends ConfigTypeSerializer<AuthHookConfig, SocketAddress> {
26+
27+
public SocketAddressTypeSerializer(final AuthHookConfig config) {
28+
super(config);
29+
}
30+
31+
@Override
32+
public SocketAddress deserialize(final Class<SocketAddress> typeClass, final Object serializedObject) {
33+
return AddressUtil.parse((String) serializedObject, null);
34+
}
35+
36+
@Override
37+
public Object serialize(final SocketAddress object) {
38+
return AddressUtil.toString(object);
39+
}
40+
41+
}

src/main/resources/viaproxy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ name: "AuthHook"
22
version: "${version}"
33
author: "RK_01 and Lenni0451"
44
main: "net.raphimc.authhook.AuthHook"
5-
min-version: "3.2.1"
5+
min-version: "3.3.6"

0 commit comments

Comments
 (0)