From 0dad0d1a043be041ba5a75b3d8f091a3cb613a8c Mon Sep 17 00:00:00 2001 From: Tech Garage Date: Thu, 25 Jan 2024 20:36:09 +0330 Subject: [PATCH] Use MinimalAPI for API back-end. --- Application/MinimalAPI/APIMappings.cs | 164 ++++++++++++++++++ Application/MinimalAPI/AuthController.cs | 86 +++++++++ .../MinimalAPI/ConfigurationController.cs | 113 ++++++++++++ Application/MinimalAPI/IPPoolController.cs | 61 +++++++ Application/MinimalAPI/ServerController.cs | 81 +++++++++ Application/MinimalAPI/UserController.cs | 118 +++++++++++++ 6 files changed, 623 insertions(+) create mode 100644 Application/MinimalAPI/APIMappings.cs create mode 100644 Application/MinimalAPI/AuthController.cs create mode 100644 Application/MinimalAPI/ConfigurationController.cs create mode 100644 Application/MinimalAPI/IPPoolController.cs create mode 100644 Application/MinimalAPI/ServerController.cs create mode 100644 Application/MinimalAPI/UserController.cs diff --git a/Application/MinimalAPI/APIMappings.cs b/Application/MinimalAPI/APIMappings.cs new file mode 100644 index 0000000..fc12024 --- /dev/null +++ b/Application/MinimalAPI/APIMappings.cs @@ -0,0 +1,164 @@ +using AutoMapper; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using MikrotikAPI; +using MTWireGuard.Application.Models; +using MTWireGuard.Application.Models.Mikrotik; +using MTWireGuard.Application.Repositories; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.IO; +using System.Linq; +using System.Security.Claims; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace MTWireGuard.Application.MinimalAPI +{ + public static class APIMappings + { + public static RouteGroupBuilder MapGeneralApi(this RouteGroupBuilder group) + { + // Retreive updates from Mikrotik + group.MapPost(Endpoints.Usage, TrafficUsageUpdate); + // Map auth endpoints + group.MapGroup(Endpoints.Auth) + .MapAuthAPI(); + // Map wireguard users endpoints + group.MapGroup(Endpoints.User) + .MapUserApi() + .RequireAuthorization(); + // Map wireguard servers endpoints + group.MapGroup(Endpoints.Server) + .MapServerApi() + .RequireAuthorization(); + // Map IP pool endpoints + group.MapGroup(Endpoints.IPPool) + .MapIPPoolsApi() + .RequireAuthorization(); + // Map configuration endpoints + group.MapGroup(Endpoints.Configuration) + .MapConfigurationApi() + .RequireAuthorization(); + + return group; + } + private static RouteGroupBuilder MapAuthAPI(this RouteGroupBuilder group) + { + group.MapGet(Endpoints.Logout, AuthController.Logout); + group.MapPost(Endpoints.Login, AuthController.Login); + return group; + } + private static RouteGroupBuilder MapUserApi(this RouteGroupBuilder group) + { + group.MapGet("/", UserController.GetAll); + group.MapGet("/{id}", UserController.GetById); + group.MapGet($"{Endpoints.QR}/{{id}}", UserController.GetQR); + group.MapGet($"{Endpoints.File}/{{id}}", UserController.GetFile); + group.MapPost("/", UserController.Create); + group.MapPut("/{id}", UserController.Update); + group.MapPatch($"{Endpoints.Sync}/{{id}}", UserController.Sync); + group.MapPatch($"{Endpoints.Activation}/{{id}}", UserController.Activation); + group.MapDelete("/{id}", UserController.Delete); + + return group; + } + private static RouteGroupBuilder MapServerApi(this RouteGroupBuilder group) + { + group.MapGet("/", ServerController.GetAll); + group.MapPost("/", ServerController.Create); + group.MapPut("/{id}", ServerController.Update); + group.MapDelete("/{id}", ServerController.Delete); + group.MapPatch($"{Endpoints.Activation}/{{id}}", ServerController.Activation); + + return group; + } + private static RouteGroupBuilder MapIPPoolsApi(this RouteGroupBuilder group) + { + group.MapGet("/", IPPoolController.GetAll); + group.MapPost("/", IPPoolController.Create); + group.MapPut("/{id}", IPPoolController.Update); + group.MapDelete("/{id}", IPPoolController.Delete); + + return group; + } + private static RouteGroupBuilder MapConfigurationApi(this RouteGroupBuilder group) + { + group.MapGet(Endpoints.Logs, async ([FromServices] IMikrotikRepository API) => await API.GetLogsAsync()). + RequireAuthorization(); + group.MapGet(Endpoints.Resources, ConfigurationController.Resources). + RequireAuthorization(); + group.MapGet(Endpoints.DNS, ConfigurationController.DNS) + .RequireAuthorization(); + group.MapPut(Endpoints.DNS, ConfigurationController.DNSUpdate) + .RequireAuthorization(); + group.MapGet(Endpoints.Identity, ConfigurationController.Identity) + .RequireAuthorization(); + group.MapPut(Endpoints.Identity, ConfigurationController.IdentityUpdate) + .RequireAuthorization(); + group.MapGet(Endpoints.Information, ConfigurationController.Information) + .RequireAuthorization(); + return group; + } + + /// + /// Retrieve and handle WG peer traffic usage + /// + public static async Task> TrafficUsageUpdate( + [FromServices] IMapper mapper, + [FromServices] DBContext dbContext, + [FromServices] IMikrotikRepository mikrotikRepository, + HttpContext context) + { + + StreamReader reader = new(context.Request.Body); + string body = await reader.ReadToEndAsync(); + + var list = Helper.ParseTrafficUsage(body); + var updates = mapper.Map>(list); + + if (updates == null || updates.Count < 1) return TypedResults.Problem("Empty data"); + + Helper.HandleUserTraffics(updates, dbContext, mikrotikRepository); + + return TypedResults.Accepted("Done"); + } + } + + internal static class Endpoints + { + // Groups + public const string Auth = "/Auth"; + public const string User = "/Users"; + public const string Server = "/Servers"; + public const string IPPool = "/IPPools"; + public const string Configuration = "/Config"; + + // Endpoints + // Auth + public const string Login = "/Login"; + public const string Logout = "/Logout"; + // Users, Servers + public const string Activation = "/Activation"; + public const string File = "/File"; + public const string QR = "/QR"; + public const string Sync = "/Sync"; + // Configuration + public const string DNS = "/DNS"; + public const string Identity = "/Identity"; + public const string Logs = "/Logs"; + public const string Resources = "/Resources"; + public const string Information = "/Information"; + // Retrival + public const string Usage = "/Usage"; + } +} diff --git a/Application/MinimalAPI/AuthController.cs b/Application/MinimalAPI/AuthController.cs new file mode 100644 index 0000000..41b80d3 --- /dev/null +++ b/Application/MinimalAPI/AuthController.cs @@ -0,0 +1,86 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Caching.Memory; +using MTWireGuard.Application.Models.Requests; +using MTWireGuard.Application.Repositories; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace MTWireGuard.Application.MinimalAPI +{ + internal class AuthController + { + public static async Task> Login([FromBody] LoginRequest login) + { + try + { + string MT_IP = Environment.GetEnvironmentVariable("MT_IP"); + string MT_USER = Environment.GetEnvironmentVariable("MT_USER"); + string MT_PASS = Environment.GetEnvironmentVariable("MT_PASS") ?? ""; + + if (login.Username == MT_USER && login.Password == MT_PASS) + { + HttpClientHandler handler = new() + { + ServerCertificateCustomValidationCallback = delegate { return true; } + }; + using HttpClient httpClient = new(handler); + using var request = new HttpRequestMessage(new HttpMethod("GET"), $"https://{MT_IP}/rest/"); + string base64authorization = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{MT_USER}:{MT_PASS}")); + request.Headers.TryAddWithoutValidation("Authorization", $"Basic {base64authorization}"); + + HttpResponseMessage response = await httpClient.SendAsync(request); + var resp = await response.Content.ReadAsStringAsync(); + + var claims = new List + { + new(ClaimTypes.Role, "Administrator"), + }; + + var claimsIdentity = new ClaimsIdentity( + claims, CookieAuthenticationDefaults.AuthenticationScheme); + + var authProperties = new AuthenticationProperties + { + AllowRefresh = true, + IsPersistent = true + }; + + var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); + + return TypedResults.SignIn(claimsPrincipal, authProperties, CookieAuthenticationDefaults.AuthenticationScheme); + } + else + { + return TypedResults.Unauthorized(); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + return TypedResults.Problem( + detail: ex.Message, + type: ex.GetType().Name); + } + } + + public static async Task Logout( + [FromServices] IMikrotikRepository API, + HttpContext context) + { + // Clear the existing external cookie + await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + var sessionId = await API.GetCurrentSessionID(); + var kill = await API.KillJob(sessionId); + return TypedResults.SignOut(); + } + } +} diff --git a/Application/MinimalAPI/ConfigurationController.cs b/Application/MinimalAPI/ConfigurationController.cs new file mode 100644 index 0000000..f529b6f --- /dev/null +++ b/Application/MinimalAPI/ConfigurationController.cs @@ -0,0 +1,113 @@ +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using MTWireGuard.Application.Models.Mikrotik; +using MTWireGuard.Application.Repositories; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AutoMapper; +using MTWireGuard.Application.Models.Models.Responses; +using MTWireGuard.Application.Models.Requests; +using MTWireGuard.Application.Models; +using MikrotikAPI.Models; +using Newtonsoft.Json; + +namespace MTWireGuard.Application.MinimalAPI +{ + internal class ConfigurationController + { + public static async Task> Resources( + [FromServices] IMikrotikRepository API) + { + var info = await API.GetInfo(); + var ramUsed = 100 - info.FreeRAMPercentage; + var hddUsed = 100 - info.FreeHDDPercentage; + + var output = new JsonResult(new + { + HDD = new + { + Total = info.TotalHDD, + Used = info.UsedHDD, + Free = info.FreeHDD, + Percentage = Convert.ToByte(hddUsed) + }, + RAM = new + { + Total = info.TotalRAM, + Used = info.UsedRAM, + Free = info.FreeRAM, + Percentage = Convert.ToByte(ramUsed) + }, + info.CPULoad, + info.UPTime + }).Value; + return TypedResults.Ok(output); + } + + public static async Task, ProblemHttpResult>> DNS( + [FromServices] IMikrotikRepository API) + { + return TypedResults.Ok(await API.GetDNS()); + } + + public static async Task> Information( + [FromServices] IMikrotikRepository API) + { + var info = await API.GetInfo(); + var identity = await API.GetName(); + var dns = await API.GetDNS(); + + var dnsValues = new List(); + dnsValues.AddRange(dns.Servers.Split(',')); + dnsValues.AddRange(dns.DynamicServers.Split(',')); + + var output = new JsonResult(new + { + Identity = identity.Name, + DNS = dnsValues, + Device = new + { + info.BoardName, + info.Architecture + }, + info.Version, + IP = Environment.GetEnvironmentVariable("MT_PUBLIC_IP") + }).Value; + return TypedResults.Ok(output); + } + + public static async Task, ProblemHttpResult>> Identity( + [FromServices] IMikrotikRepository API) + { + return TypedResults.Ok(await API.GetName()); + } + + public static async Task> IdentityUpdate( + [FromServices] IMikrotikRepository API, + [FromServices] IMapper mapper, + [FromBody] UpdateIdentityRequest request) + { + var model = mapper.Map(request); + var update = await API.SetName(model); + var message = mapper.Map(update); + return TypedResults.Ok(message); + } + + public static async Task> DNSUpdate( + [FromServices] IMikrotikRepository API, + [FromServices] IMapper mapper, + [FromBody] UpdateDNSRequest request) + { + var model = mapper.Map(request); + model.Servers.Remove(string.Empty); + model.Servers.Remove(" "); + var update = await API.SetDNS(model); + var message = mapper.Map(update); + return TypedResults.Ok(message); + } + } +} diff --git a/Application/MinimalAPI/IPPoolController.cs b/Application/MinimalAPI/IPPoolController.cs new file mode 100644 index 0000000..334e1c4 --- /dev/null +++ b/Application/MinimalAPI/IPPoolController.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using MTWireGuard.Application.Models.Mikrotik; +using MTWireGuard.Application.Repositories; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AutoMapper; +using MTWireGuard.Application.Models.Models.Responses; +using MTWireGuard.Application.Models.Requests; +using MTWireGuard.Application.Models; +using MikrotikAPI.Models; + +namespace MTWireGuard.Application.MinimalAPI +{ + internal class IPPoolController + { + public static async Task>, NotFound>> GetAll([FromServices] IMikrotikRepository API) + { + var ippools = await API.GetIPPools(); + return ippools.Any() ? TypedResults.Ok(ippools) : TypedResults.NotFound(); + } + + public static async Task> Create( + [FromServices] IMikrotikRepository API, + [FromServices] IMapper mapper, + [FromBody] CreatePoolRequest request) + { + var model = mapper.Map(request); + var make = await API.CreateIPPool(model); + var message = mapper.Map(make); + return TypedResults.Ok(message); + } + + public static async Task> Update( + [FromServices] IMikrotikRepository API, + [FromServices] IMapper mapper, + int id, + [FromBody] UpdateIPPoolRequest request) + { + request.Id = id; + var model = mapper.Map(request); + var update = await API.UpdateIPPool(model); + var message = mapper.Map(update); + return TypedResults.Ok(message); + } + + public static async Task> Delete( + [FromServices] IMikrotikRepository API, + [FromServices] IMapper mapper, + int id) + { + var delete = await API.DeleteIPPool(id); + var message = mapper.Map(delete); + return TypedResults.Ok(message); + } + } +} diff --git a/Application/MinimalAPI/ServerController.cs b/Application/MinimalAPI/ServerController.cs new file mode 100644 index 0000000..955abf8 --- /dev/null +++ b/Application/MinimalAPI/ServerController.cs @@ -0,0 +1,81 @@ +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using MTWireGuard.Application.Models.Mikrotik; +using MTWireGuard.Application.Repositories; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AutoMapper; +using MTWireGuard.Application.Models.Models.Responses; +using MTWireGuard.Application.Models.Requests; +using MTWireGuard.Application.Models; + +namespace MTWireGuard.Application.MinimalAPI +{ + internal class ServerController + { + public static async Task>, NotFound>> GetAll([FromServices] IMikrotikRepository API) + { + var servers = await API.GetServersAsync(); + //if (servers.Any()) + //{ + // var traffics = await API.GetServersTraffic(); + // return TypedResults.Ok(servers); + //} + //else + // TypedResults.NotFound(); + return servers.Any() ? TypedResults.Ok(servers) : TypedResults.NotFound(); + } + + public static async Task> Create( + [FromServices] IMikrotikRepository API, + [FromServices] IMapper mapper, + [FromBody] CreateServerRequest request) + { + var model = mapper.Map(request); + var make = await API.CreateServer(model); + var message = mapper.Map(make); + return TypedResults.Ok(message); + } + + public static async Task> Update( + [FromServices] IMikrotikRepository API, + [FromServices] IMapper mapper, + int id, + [FromBody] UpdateServerRequest request) + //HttpRequest httpRequest) + { + //var request = await httpRequest.ReadFromJsonAsync(); + request.Id = id; + var model = mapper.Map(request); + var update = await API.UpdateServer(model); + var message = mapper.Map(update); + return TypedResults.Ok(message); + } + + public static async Task> Delete( + [FromServices] IMikrotikRepository API, + [FromServices] IMapper mapper, + [FromRoute] int id) + { + var delete = await API.DeleteServer(id); + var message = mapper.Map(delete); + return TypedResults.Ok(message); + } + + public static async Task> Activation( + [FromServices] IMikrotikRepository API, + [FromServices] IMapper mapper, + [FromRoute] int id, + ChangeStateRequest request) + { + request.Id = id; + var active = (!request.Enabled) ? await API.EnableServer(request.Id) : await API.DisableServer(request.Id); + var message = mapper.Map(active); + return TypedResults.Ok(message); + } + } +} diff --git a/Application/MinimalAPI/UserController.cs b/Application/MinimalAPI/UserController.cs new file mode 100644 index 0000000..219c4b3 --- /dev/null +++ b/Application/MinimalAPI/UserController.cs @@ -0,0 +1,118 @@ +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Caching.Memory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MTWireGuard.Application.Repositories; +using Microsoft.AspNetCore.Mvc; +using MTWireGuard.Application.Models.Mikrotik; +using AutoMapper; +using MTWireGuard.Application.Models.Requests; +using MTWireGuard.Application.Models.Models.Responses; +using MTWireGuard.Application.Models; + +namespace MTWireGuard.Application.MinimalAPI +{ + internal class UserController + { + public static async Task>, NotFound>> GetAll([FromServices] IMikrotikRepository API, HttpContext context) + { + var script = await API.RunScript("SendActivityUpdates"); + var users = await API.GetUsersAsync(); + if (users.Count > 0) + return TypedResults.Ok(users); + return TypedResults.NotFound(); + } + + public static async Task, NotFound>> GetById([FromServices] IMikrotikRepository API, int id) + { + var user = await API.GetUser(id); + if (user != null) + return TypedResults.Ok(user); + return TypedResults.NotFound(); + } + + public static async Task> GetQR([FromServices] IMikrotikRepository API, int id) + { + string config = await API.GetQRCodeBase64(id); + return TypedResults.Ok(config); + } + + public static async Task GetFile([FromServices] IMikrotikRepository API, int id) + { + string config = await API.GetUserTunnelConfig(id); + + byte[] bytesInStream = Encoding.UTF8.GetBytes(config); + + var user = await API.GetUser(id); + string filename = string.IsNullOrWhiteSpace(user.Name) ? user.Interface : user.Name; + return TypedResults.File( + fileContents: bytesInStream, + fileDownloadName: filename); + } + + public static async Task> Create( + [FromServices] IMikrotikRepository API, + [FromServices] IMapper mapper, + [FromBody] CreateClientRequest request) + //HttpRequest request) + { + //var ucm = await request.ReadFromJsonAsync(); + var model = mapper.Map(request); + var make = await API.CreateUser(model); + var message = mapper.Map(make); + return TypedResults.Ok(message); + } + + public static async Task> Update( + [FromServices] IMikrotikRepository API, + [FromServices] IMapper mapper, + int id, + UpdateClientRequest request) + { + request.ID = id; + var model = mapper.Map(request); + var update = await API.UpdateUser(model); + var message = mapper.Map(update); + return TypedResults.Ok(message); + } + + public static async Task> Sync( + [FromServices] IMikrotikRepository API, + [FromServices] IMapper mapper, + int id, + SyncUserRequest request) + { + request.ID = id; + var model = mapper.Map(request); + var update = await API.SyncUser(model); + var message = mapper.Map(update); + return TypedResults.Ok(message); + } + + public static async Task> Delete( + [FromServices] IMikrotikRepository API, + [FromServices] IMapper mapper, + [FromRoute] int id) + { + var delete = await API.DeleteUser(id); + var message = mapper.Map(delete); + return TypedResults.Ok(message); + } + + public static async Task> Activation( + [FromServices] IMikrotikRepository API, + [FromServices] IMapper mapper, + [FromRoute] int id, + ChangeStateRequest request) + { + request.Id = id; + var active = (!request.Enabled) ? await API.EnableUser(request.Id) : await API.DisableUser(request.Id); + var message = mapper.Map(active); + return TypedResults.Ok(message); + } + } +}