Skip to content

Commit

Permalink
JSON API
Browse files Browse the repository at this point in the history
  • Loading branch information
Istador committed Feb 10, 2025
1 parent 5c5b37a commit 594ceb7
Show file tree
Hide file tree
Showing 14 changed files with 783 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@
/Server/obj/
/Shared/bin/
/Shared/obj/

/Server/**/.gitignore
/Server/**/*.env
/Server/**/*.md
/Server/**/*.sh
1 change: 1 addition & 0 deletions Server/JsonApi/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/test.env
47 changes: 47 additions & 0 deletions Server/JsonApi/ApiPacket.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Net.Sockets;

using System.Text;
using System.Text.Json;

using Shared;

namespace Server.JsonApi;

public class ApiPacket {
public const ushort MAX_PACKET_SIZE = 512; // in bytes (including 20 byte header)


public ApiRequest? API_JSON_REQUEST { get; set; }


public static async Task<ApiPacket?> Read(Context ctx, string header) {
string reqStr = header + await ApiPacket.GetRequestStr(ctx);

ApiPacket? p = null;
try { p = JsonSerializer.Deserialize<ApiPacket>(reqStr); }
catch {
JsonApi.Logger.Warn($"Invalid packet deserialize from {ctx.socket.RemoteEndPoint}: {reqStr}.");
return null;
}

if (p == null) {
JsonApi.Logger.Warn($"Invalid packet from {ctx.socket.RemoteEndPoint}: {reqStr}.");
return null;
}

if (p.API_JSON_REQUEST == null) {
JsonApi.Logger.Warn($"Invalid request from {ctx.socket.RemoteEndPoint}: {reqStr}.");
return null;
}

return p;
}


private static async Task<string> GetRequestStr(Context ctx) {
byte[] buffer = new byte[ApiPacket.MAX_PACKET_SIZE - Constants.HeaderSize];
int size = await ctx.socket.ReceiveAsync(buffer, SocketFlags.None);
return Encoding.UTF8.GetString(buffer, 0, size);
}
}

62 changes: 62 additions & 0 deletions Server/JsonApi/ApiRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
namespace Server.JsonApi;

using System.Text.Json;
using System.Text.Json.Nodes;

using TypesDictionary = Dictionary<string, Func<Context, Task<bool>>>;

public class ApiRequest {
public string? Token { get; set; }
public string? Type { get; set; }
public JsonNode? Data { get; set; }


private static TypesDictionary Types = new TypesDictionary() {
["Status"] = async (Context ctx) => await ApiRequestStatus.Send(ctx),
["Command"] = async (Context ctx) => await ApiRequestCommand.Send(ctx),
["Permissions"] = async (Context ctx) => await ApiRequestPermissions.Send(ctx),
};


public string? GetStringData() {
if (this.Data is JsonValue) {
JsonElement val = this.Data.GetValue<JsonElement>();
JsonValueKind kind = val.ValueKind;
if (kind == JsonValueKind.String) { return val.GetString(); }
}
return null;
}


public async Task<bool> Process(Context ctx) {
if (this.Type != null) {
return await ApiRequest.Types[this.Type](ctx);
}
return false;
}


public bool IsValid(Context ctx) {
if (this.Token == null) {
JsonApi.Logger.Warn($"Invalid request missing Token from {ctx.socket.RemoteEndPoint}.");
return false;
}

if (this.Type == null) {
JsonApi.Logger.Warn($"Invalid request missing Type from {ctx.socket.RemoteEndPoint}.");
return false;
}

if (!ApiRequest.Types.ContainsKey(this.Type)) {
JsonApi.Logger.Warn($"Invalid Type \"{this.Type}\" from {ctx.socket.RemoteEndPoint}.");
return false;
}

if (!Settings.Instance.JsonApi.Tokens.ContainsKey(this.Token)) {
JsonApi.Logger.Warn($"Invalid Token from {ctx.socket.RemoteEndPoint}.");
return false;
}

return true;
}
}
69 changes: 69 additions & 0 deletions Server/JsonApi/ApiRequestCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
namespace Server.JsonApi;

public static class ApiRequestCommand {
public static async Task<bool> Send(Context ctx) {
if (!ctx.HasPermission("Commands")) {
await Response.Send(ctx, "Error: Missing Commands permission.");
return true;
}

if (!ApiRequestCommand.IsValid(ctx)) {
return false;
}

string input = ctx.request!.GetStringData()!;
string command = input.Split(" ")[0];

// help doesn't need permissions and is invidualized to the token
if (command == "help") {
List<string> commands = new List<string>();
commands.Add("help");
commands.AddRange(
ctx.Permissions
.Where(str => str.StartsWith("Commands/"))
.Select(str => str.Substring(9))
.Where(cmd => CommandHandler.Handlers.ContainsKey(cmd))
);
string commandsStr = string.Join(", ", commands);

await Response.Send(ctx, $"Valid commands: {commandsStr}");
return true;
}

// no permissions
if (! ctx.HasPermission($"Commands/{command}")) {
await Response.Send(ctx, $"Error: Missing Commands/{command} permission.");
return true;
}

// execute command
JsonApi.Logger.Info($"[Commands] " + input);
await Response.Send(ctx, CommandHandler.GetResult(input));
return true;
}


private static bool IsValid(Context ctx) {
string? command = ctx.request!.GetStringData();

if (command == null) {
JsonApi.Logger.Warn($"[Commands] Invalid request. Data is not a \"System.String\" from {ctx.socket.RemoteEndPoint}.");
return false;
}

return true;
}


private class Response {
public string[]? Output { get; set; }


public static async Task Send(Context ctx, CommandHandler.Response response)
{
Response resp = new Response();
resp.Output = response.ReturnStrings;
await ctx.Send(resp);
}
}
}
21 changes: 21 additions & 0 deletions Server/JsonApi/ApiRequestPermissions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Server.JsonApi;

public static class ApiRequestPermissions {
public static async Task<bool> Send(Context ctx) {
await Response.Send(ctx);
return true;
}


private class Response {
public string[]? Permissions { get; set; }


public static async Task Send(Context ctx)
{
Response resp = new Response();
resp.Permissions = ctx.Permissions.ToArray();
await ctx.Send(resp);
}
}
}
Loading

0 comments on commit 594ceb7

Please sign in to comment.