diff --git a/MoonlightServers.ApiServer/Http/Controllers/Client/ServerFileSystemController.cs b/MoonlightServers.ApiServer/Http/Controllers/Client/ServerFileSystemController.cs index 1f6fe0a..4902c31 100644 --- a/MoonlightServers.ApiServer/Http/Controllers/Client/ServerFileSystemController.cs +++ b/MoonlightServers.ApiServer/Http/Controllers/Client/ServerFileSystemController.cs @@ -78,7 +78,7 @@ public class ServerFileSystemController : Controller } [HttpGet("{serverId:int}/files/upload")] - public async Task Upload([FromRoute] int serverId, [FromQuery] string path) + public async Task Upload([FromRoute] int serverId) { var server = await GetServerById(serverId); @@ -88,9 +88,8 @@ public class ServerFileSystemController : Controller { parameters.Add("type", "upload"); parameters.Add("serverId", server.Id); - parameters.Add("path", path); }, - TimeSpan.FromMinutes(5) + TimeSpan.FromMinutes(1) ); var url = ""; @@ -104,6 +103,34 @@ public class ServerFileSystemController : Controller UploadUrl = url }; } + + [HttpGet("{serverId:int}/files/download")] + public async Task Download([FromRoute] int serverId, [FromQuery] string path) + { + var server = await GetServerById(serverId); + + var accessToken = NodeService.CreateAccessToken( + server.Node, + parameters => + { + parameters.Add("type", "download"); + parameters.Add("path", path); + parameters.Add("serverId", server.Id); + }, + TimeSpan.FromMinutes(1) + ); + + var url = ""; + + url += server.Node.UseSsl ? "https://" : "http://"; + url += $"{server.Node.Fqdn}:{server.Node.HttpPort}/"; + url += $"api/servers/download?token={accessToken}"; + + return new ServerFilesDownloadResponse() + { + DownloadUrl = url + }; + } private async Task GetServerById(int serverId) { diff --git a/MoonlightServers.Daemon/Helpers/ServerFileSystem.cs b/MoonlightServers.Daemon/Helpers/ServerFileSystem.cs index 0c4234f..4f68b0d 100644 --- a/MoonlightServers.Daemon/Helpers/ServerFileSystem.cs +++ b/MoonlightServers.Daemon/Helpers/ServerFileSystem.cs @@ -73,6 +73,19 @@ public class ServerFileSystem return Task.CompletedTask; } + + public Task Read(string inputPath, Func onHandle) + { + var path = Normalize(inputPath); + + FileSystem.OpenFile(path, stream => + { + // No try catch here because the safe fs abstraction already handles every error occuring in the handle + onHandle.Invoke(stream).Wait(); + }); + + return Task.CompletedTask; + } private string Normalize(string path) { diff --git a/MoonlightServers.Daemon/Http/Controllers/Servers/DownloadController.cs b/MoonlightServers.Daemon/Http/Controllers/Servers/DownloadController.cs new file mode 100644 index 0000000..ea445f5 --- /dev/null +++ b/MoonlightServers.Daemon/Http/Controllers/Servers/DownloadController.cs @@ -0,0 +1,67 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using MoonCore.Exceptions; +using MoonlightServers.Daemon.Configuration; +using MoonlightServers.Daemon.Helpers; +using MoonlightServers.Daemon.Services; + +namespace MoonlightServers.Daemon.Http.Controllers.Servers; + +[AllowAnonymous] +[ApiController] +[Route("api/servers/download")] +public class DownloadController : Controller +{ + private readonly AccessTokenHelper AccessTokenHelper; + private readonly AppConfiguration Configuration; + private readonly ServerService ServerService; + + public DownloadController( + AccessTokenHelper accessTokenHelper, + ServerService serverService, + AppConfiguration configuration + ) + { + AccessTokenHelper = accessTokenHelper; + ServerService = serverService; + Configuration = configuration; + } + + [HttpGet] + public async Task Download([FromQuery] string token) + { + #region Token validation + + if (!AccessTokenHelper.Process(token, out var claims)) + throw new HttpApiException("Invalid access token provided", 401); + + var typeClaim = claims.FirstOrDefault(x => x.Type == "type"); + + if (typeClaim == null || typeClaim.Value != "download") + throw new HttpApiException("Invalid access token provided: Missing or invalid type", 401); + + var serverIdClaim = claims.FirstOrDefault(x => x.Type == "serverId"); + + if (serverIdClaim == null || !int.TryParse(serverIdClaim.Value, out var serverId)) + throw new HttpApiException("Invalid access token provided: Missing or invalid server id", 401); + + var pathClaim = claims.FirstOrDefault(x => x.Type == "path"); + + if(pathClaim == null || string.IsNullOrEmpty(pathClaim.Value)) + throw new HttpApiException("Invalid access token provided: Missing or invalid path", 401); + + #endregion + + var server = ServerService.GetServer(serverId); + + if (server == null) + throw new HttpApiException("No server with this id found", 404); + + var path = pathClaim.Value; + + await server.FileSystem.Read(path, async dataStream => + { + await Results.File(dataStream).ExecuteAsync(HttpContext); + }); + } +} \ No newline at end of file diff --git a/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj b/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj index e30d837..598a111 100644 --- a/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj +++ b/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj @@ -11,7 +11,7 @@ - + diff --git a/MoonlightServers.Frontend/Helpers/ServerFileSystemProvider.cs b/MoonlightServers.Frontend/Helpers/ServerFileSystemProvider.cs index ee511b7..ac98f58 100644 --- a/MoonlightServers.Frontend/Helpers/ServerFileSystemProvider.cs +++ b/MoonlightServers.Frontend/Helpers/ServerFileSystemProvider.cs @@ -53,8 +53,8 @@ public class ServerFileSystemProvider : IFileSystemProvider await FileSystemService.Mkdir(ServerId, path); } - public Task Read(string path) + public async Task Read(string path) { - throw new NotImplementedException(); + return await FileSystemService.Download(ServerId, path); } } \ No newline at end of file diff --git a/MoonlightServers.Frontend/Services/ServerFileSystemService.cs b/MoonlightServers.Frontend/Services/ServerFileSystemService.cs index 6e12e68..86465ef 100644 --- a/MoonlightServers.Frontend/Services/ServerFileSystemService.cs +++ b/MoonlightServers.Frontend/Services/ServerFileSystemService.cs @@ -45,7 +45,7 @@ public class ServerFileSystemService public async Task Upload(int serverId, string path, Stream dataStream) { var uploadSession = await ApiClient.GetJson( - $"api/client/servers/{serverId}/files/upload?path={path}" + $"api/client/servers/{serverId}/files/upload" ); using var httpClient = new HttpClient(); @@ -55,4 +55,15 @@ public class ServerFileSystemService await httpClient.PostAsync(uploadSession.UploadUrl, content); } + + public async Task Download(int serverId, string path) + { + var downloadSession = await ApiClient.GetJson( + $"api/client/servers/{serverId}/files/download?path={path}" + ); + + using var httpClient = new HttpClient(); + + return await httpClient.GetStreamAsync(downloadSession.DownloadUrl); + } } \ No newline at end of file diff --git a/MoonlightServers.Shared/Http/Responses/Client/Servers/Files/ServerFilesDownloadResponse.cs b/MoonlightServers.Shared/Http/Responses/Client/Servers/Files/ServerFilesDownloadResponse.cs new file mode 100644 index 0000000..7c77fb0 --- /dev/null +++ b/MoonlightServers.Shared/Http/Responses/Client/Servers/Files/ServerFilesDownloadResponse.cs @@ -0,0 +1,6 @@ +namespace MoonlightServers.Shared.Http.Responses.Client.Servers.Files; + +public class ServerFilesDownloadResponse +{ + public string DownloadUrl { get; set; } +} \ No newline at end of file