diff --git a/MicrobotApi/ConnectionManager.cs b/MicrobotApi/ConnectionManager.cs new file mode 100644 index 0000000..64fe8a9 --- /dev/null +++ b/MicrobotApi/ConnectionManager.cs @@ -0,0 +1,6 @@ +namespace MicrobotApi; + +public class ConnectionManager +{ + public readonly List Connections = []; +} \ No newline at end of file diff --git a/MicrobotApi/ChatHub.cs b/MicrobotApi/MicrobotHub.cs similarity index 67% rename from MicrobotApi/ChatHub.cs rename to MicrobotApi/MicrobotHub.cs index 8926a54..1b44679 100644 --- a/MicrobotApi/ChatHub.cs +++ b/MicrobotApi/MicrobotHub.cs @@ -1,12 +1,10 @@ -using Microsoft.AspNetCore.SignalR; +using MicrobotApi.Models; +using Microsoft.AspNetCore.SignalR; namespace MicrobotApi; -public class ChatHub : Hub +public class MicrobotHub : Hub { - private readonly static List _connections = - new List(); - public async Task SendMessage(string user, string message) { await Clients.All.SendAsync("ReceiveMessage", user,message); @@ -15,7 +13,6 @@ public async Task SendMessage(string user, string message) public async Task AddToGroup(string groupName) { await Groups.AddToGroupAsync(Context.ConnectionId, groupName); - await Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId} has joined the group {groupName}."); } @@ -25,4 +22,10 @@ public async Task RemoveFromGroup(string groupName) await Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId} has left the group {groupName}."); } + + public async Task SendBotPlugins(SendBotPluginRequestModel sendBotPluginRequestModel) + { + await Clients.Group(sendBotPluginRequestModel.Group) + .SendAsync("ReceiveBotPlugins", sendBotPluginRequestModel.Plugins); + } } \ No newline at end of file diff --git a/MicrobotApi/Models/IHubRequestModel.cs b/MicrobotApi/Models/IHubRequestModel.cs new file mode 100644 index 0000000..208bb6d --- /dev/null +++ b/MicrobotApi/Models/IHubRequestModel.cs @@ -0,0 +1,6 @@ +namespace MicrobotApi.Models; + +public interface IHubRequestModel +{ + public string Group { get; set; } +} \ No newline at end of file diff --git a/MicrobotApi/Models/PluginRequestModel.cs b/MicrobotApi/Models/PluginRequestModel.cs new file mode 100644 index 0000000..5c6aff9 --- /dev/null +++ b/MicrobotApi/Models/PluginRequestModel.cs @@ -0,0 +1,7 @@ +namespace MicrobotApi.Models; + +public class PluginRequestModel +{ + public string Name { get; set; } + public bool Active { get; set; } +} \ No newline at end of file diff --git a/MicrobotApi/Models/SendBotPluginRequestModel.cs b/MicrobotApi/Models/SendBotPluginRequestModel.cs new file mode 100644 index 0000000..0988720 --- /dev/null +++ b/MicrobotApi/Models/SendBotPluginRequestModel.cs @@ -0,0 +1,7 @@ +namespace MicrobotApi.Models; + +public class SendBotPluginRequestModel : IHubRequestModel +{ + public string Group { get; set; } + public List Plugins { get; set; } +} \ No newline at end of file diff --git a/MicrobotApi/Program.cs b/MicrobotApi/Program.cs index d925e7f..3422723 100644 --- a/MicrobotApi/Program.cs +++ b/MicrobotApi/Program.cs @@ -1,4 +1,6 @@ using MicrobotApi; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SignalR; var builder = WebApplication.CreateBuilder(args); @@ -21,7 +23,15 @@ // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -builder.Services.AddSignalR(); +builder.Services + .AddSignalR() + .AddHubOptions(options => + { + // Local filters will run second + options.AddFilter(); + }); + +builder.Services.AddSingleton(); var app = builder.Build(); @@ -37,16 +47,20 @@ app.MapGet("/token", () => { var token = Guid.NewGuid(); + var connectionManager = app.Services.GetService(); + connectionManager?.Connections.Add(token.ToString()); return token; }) .WithName("getToken") .WithOpenApi(); +app.MapPost("/token", async ([FromBody] TokenRequestModel tokenRequestModel) => + { + var connectionManager = app.Services.GetService(); + return connectionManager?.Connections.Contains(tokenRequestModel.Token); + }) + .WithName("Check Token Validity") + .WithOpenApi(); app.UseCors(); -app.MapHub("/microbot"); - -app.Run(); +app.MapHub("/microbot"); -record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) -{ - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); -} \ No newline at end of file +app.Run(); \ No newline at end of file diff --git a/MicrobotApi/TokenFilter.cs b/MicrobotApi/TokenFilter.cs new file mode 100644 index 0000000..31c9100 --- /dev/null +++ b/MicrobotApi/TokenFilter.cs @@ -0,0 +1,67 @@ +using Microsoft.AspNetCore.SignalR; + +namespace MicrobotApi; + +public class TokenFilter : IHubFilter +{ + private readonly ConnectionManager _connectionManager; + private const string invalidTokenMessage = "Invalid token!"; + + public TokenFilter(ConnectionManager connectionManager) + { + _connectionManager = connectionManager; + } + public async ValueTask InvokeMethodAsync( + HubInvocationContext invocationContext, Func> next) + { + + var token = GetToken(invocationContext.Context); + + var exists = _connectionManager.Connections.Contains(token); + if (!exists) + throw new UnauthorizedAccessException(invalidTokenMessage); + + Console.WriteLine($"Calling hub method '{invocationContext.HubMethodName}'"); + try + { + return await next(invocationContext); + } + catch (Exception ex) + { + Console.WriteLine($"Exception calling '{invocationContext.HubMethodName}': {ex}"); + throw; + } + } + + // Optional method + public Task OnConnectedAsync(HubLifetimeContext context, Func next) + { + return next(context); + } + + // Optional method + public Task OnDisconnectedAsync( + HubLifetimeContext context, Exception exception, Func next) + { + return next(context, exception); + } + + private string? GetToken(HubCallerContext context) + { + var httpContext = context.GetHttpContext(); + + var validHttpRequest = httpContext is { Request.Headers: not null } && httpContext.Request.Headers.Any(); + + if (!validHttpRequest + || (string.IsNullOrWhiteSpace(httpContext?.Request.Headers?["token"]) + && string.IsNullOrWhiteSpace(httpContext?.Request.Query["token"].FirstOrDefault()))) + { + return null; + } + + var token = httpContext.Request.Headers?["token"].FirstOrDefault() + ?? httpContext.Request.Query["token"].FirstOrDefault(); + + return token; + } +} \ No newline at end of file diff --git a/MicrobotApi/TokenRequestModel.cs b/MicrobotApi/TokenRequestModel.cs new file mode 100644 index 0000000..3b8a768 --- /dev/null +++ b/MicrobotApi/TokenRequestModel.cs @@ -0,0 +1,6 @@ +namespace MicrobotApi; + +public class TokenRequestModel +{ + public String Token { get; set; } +} \ No newline at end of file