Skip to content

Commit dc2bbd3

Browse files
authored
feat: basic auth for all api endpoints (#22)
1 parent a6845c7 commit dc2bbd3

File tree

5 files changed

+154
-3
lines changed

5 files changed

+154
-3
lines changed

Plugin.cs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
using System.Reflection;
1+
using System.Collections.Generic;
2+
using System.Reflection;
23
using BepInEx;
4+
using BepInEx.Configuration;
35
using BepInEx.Logging;
46
using BepInEx.Unity.IL2CPP;
57
using Bloodstone.API;
@@ -15,18 +17,34 @@ namespace v_rising_discord_bot_companion;
1517
[Reloadable]
1618
public class Plugin : BasePlugin {
1719

18-
public static ManualLogSource Logger = null!;
20+
public static ManualLogSource Logger { get; private set; } = null!;
21+
public static Plugin Instance { get; private set; } = null!;
1922
private Harmony? _harmony;
2023
private Component? _queryDispatcher;
2124

25+
private PluginConfig? _pluginConfig;
26+
private ConfigEntry<string> _basicAuthUsers;
27+
28+
public Plugin() {
29+
30+
Instance = this;
31+
Logger = Log;
32+
33+
_basicAuthUsers = Config.Bind(
34+
"Authentication",
35+
"BasicAuthUsers",
36+
"",
37+
"A list of comma separated username:password entries that are allowed to query the HTTP API."
38+
);
39+
}
40+
2241
public override void Load() {
2342

2443
if (!VWorld.IsServer) {
2544
Log.LogInfo($"Plugin {MyPluginInfo.PLUGIN_GUID} must be installed on the server side.");
2645
return;
2746
}
2847

29-
Logger = Log;
3048

3149
// Plugin startup logic
3250
Log.LogInfo($"Plugin {MyPluginInfo.PLUGIN_GUID} version {MyPluginInfo.PLUGIN_VERSION} is loaded!");
@@ -45,6 +63,32 @@ public override bool Unload() {
4563
if (_queryDispatcher != null) {
4664
Object.Destroy(_queryDispatcher);
4765
}
66+
_pluginConfig = null;
4867
return true;
4968
}
69+
70+
public PluginConfig GetPluginConfig() {
71+
_pluginConfig ??= ParsePluginConfig();
72+
return (PluginConfig) _pluginConfig;
73+
}
74+
75+
private PluginConfig ParsePluginConfig() {
76+
77+
var basicAuthUsers = new List<BasicAuthUser>();
78+
foreach (var basicAuthUser in _basicAuthUsers.Value.Split(",")) {
79+
var parts = basicAuthUser.Split(":");
80+
if (parts.Length == 2) {
81+
basicAuthUsers.Add(
82+
new BasicAuthUser(
83+
Username: parts[0].Trim(),
84+
Password: parts[1].Trim()
85+
)
86+
);
87+
}
88+
}
89+
90+
return new PluginConfig(
91+
BasicAuthUsers: basicAuthUsers
92+
);
93+
}
5094
}

PluginConfig.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.Collections.Generic;
2+
3+
namespace v_rising_discord_bot_companion;
4+
5+
public readonly record struct PluginConfig(
6+
List<BasicAuthUser> BasicAuthUsers
7+
);
8+
9+
public readonly record struct BasicAuthUser(
10+
string Username,
11+
string Password
12+
);

command/HttpReceiveServicePatches.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System.Linq;
2+
using HarmonyLib;
3+
using Il2CppSystem.Net;
4+
using Il2CppSystem.Security.Principal;
5+
using ProjectM.Network;
6+
7+
namespace v_rising_discord_bot_companion.command;
8+
9+
[HarmonyPatch(typeof(HttpServiceReceiveThread))]
10+
public class HttpReceiveServicePatches {
11+
12+
[HarmonyPrefix]
13+
[HarmonyPatch("IsAllowed")]
14+
public static bool IsAllowed(HttpListenerContext context, ref bool __result) {
15+
16+
var pluginConfig = Plugin.Instance.GetPluginConfig();
17+
18+
if (pluginConfig.BasicAuthUsers.Count <= 0) {
19+
return true;
20+
}
21+
22+
var currentBasicAuthUser = ParseBasicAuthUser(context);
23+
if (!currentBasicAuthUser.HasValue) {
24+
__result = false;
25+
return false;
26+
}
27+
28+
__result = IsAuthorized((BasicAuthUser) currentBasicAuthUser);
29+
return __result;
30+
}
31+
32+
private static BasicAuthUser? ParseBasicAuthUser(HttpListenerContext context) {
33+
34+
context.ParseAuthentication(AuthenticationSchemes.Basic);
35+
36+
if (context.user == null) {
37+
return null;
38+
}
39+
40+
var principal = context.user.TryCast<GenericPrincipal>();
41+
var identity = principal?.m_identity.TryCast<HttpListenerBasicIdentity>();
42+
43+
if (identity == null) {
44+
return null;
45+
}
46+
47+
var username = identity.Name;
48+
var password = identity.password;
49+
50+
if (username == null || password == null) {
51+
return null;
52+
}
53+
54+
return new BasicAuthUser(
55+
Username: username,
56+
Password: password
57+
);
58+
}
59+
60+
private static bool IsAuthorized(BasicAuthUser currentBasicAuthUser) {
61+
return Plugin.Instance.GetPluginConfig().BasicAuthUsers
62+
.Count(it => it.Username.Equals(currentBasicAuthUser.Username) && it.Password.Equals(currentBasicAuthUser.Password)) == 1;
63+
}
64+
}

command/ServerWebAPISystemPatches.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public class ServerWebAPISystemPatches {
3030
public static void OnCreate(ServerWebAPISystem __instance) {
3131

3232
if (!SettingsManager.ServerHostSettings.API.Enabled) {
33+
Plugin.Logger.LogInfo($"HTTP API is not enabled.");
3334
return;
3435
}
3536

@@ -50,6 +51,8 @@ public static void OnCreate(ServerWebAPISystem __instance) {
5051
"GET",
5152
BuildAdapter(_ => VampireDownedServerEventSystemPatches.getPvpKills())
5253
));
54+
55+
Plugin.Logger.LogInfo($"Added v-rising-discord-bot endpoints.");
5356
}
5457

5558
private static HttpServiceReceiveThread.RequestHandler BuildAdapter(Func<HttpListenerContext, object> commandHandler) {

http-requests.http

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,31 @@ GET http://localhost:25570/v-rising-discord-bot/player-activities
66

77
### Get pvp kills
88
GET http://localhost:25570/v-rising-discord-bot/pvp-kills
9+
10+
### Metrics
11+
GET http://localhost:25570/metrics
12+
13+
### Console
14+
GET http://localhost:25570/console/v1?input=sendserverannouncement some message
15+
16+
### Messages
17+
POST http://localhost:25570/api/message/v1
18+
Content-Type: application/json
19+
20+
{
21+
"message": "the actual message"
22+
}
23+
24+
### Shutdown
25+
POST http://localhost:25570/api/shutdown/v1
26+
Content-Type: application/json
27+
28+
{
29+
"shutdown": true
30+
}
31+
32+
### Save
33+
POST http://localhost:25570/api/save/v1
34+
Content-Type: application/json
35+
36+
{}

0 commit comments

Comments
 (0)