Implemented download in the file manager. Made file access jwt more modular

This commit is contained in:
Marcel Baumgartner
2024-02-06 22:23:47 +01:00
parent 26ed50c94b
commit 423616b9f3
5 changed files with 115 additions and 15 deletions

View File

@@ -0,0 +1,57 @@
using Microsoft.AspNetCore.Mvc;
using MoonCore.Helpers;
using Moonlight.Core.Services.Utils;
using Moonlight.Features.FileManager.Services;
namespace Moonlight.Features.FileManager.Http.Controllers;
[ApiController]
[Route("api/download")]
public class DownloadController : Controller
{
private readonly JwtService JwtService;
private readonly SharedFileAccessService SharedFileAccessService;
public DownloadController(JwtService jwtService, SharedFileAccessService sharedFileAccessService)
{
JwtService = jwtService;
SharedFileAccessService = sharedFileAccessService;
}
[HttpGet]
public async Task<ActionResult> Upload([FromQuery(Name = "token")] string downloadToken, [FromQuery(Name = "name")] string name)
{
if (name.Contains(".."))
{
Logger.Warn($"A user tried to access a file via path transversal. Name: {name}");
return NotFound();
}
// Validate request
if (!await JwtService.Validate(downloadToken, "FileAccess"))
return StatusCode(403);
var downloadContext = await JwtService.Decode(downloadToken);
if (!downloadContext.ContainsKey("FileAccessId"))
return BadRequest();
if (!int.TryParse(downloadContext["FileAccessId"], out int fileAccessId))
return BadRequest();
// Load file access for this file
var fileAccess = await SharedFileAccessService.Get(fileAccessId);
if (fileAccess == null)
return BadRequest("Invalid file access id");
var files = await fileAccess.List();
if (files.All(x => !x.IsFile && x.Name != name))
return NotFound();
var stream = await fileAccess.ReadFileStream(name);
return File(stream, "application/octet-stream", name);
}
}

View File

@@ -45,7 +45,7 @@ public class UploadController : Controller
return BadRequest("Too many files sent");
// Validate request
if (!await JwtService.Validate(uploadToken, "FileUpload"))
if (!await JwtService.Validate(uploadToken, "FileAccess"))
return StatusCode(403);
var uploadContext = await JwtService.Decode(uploadToken);

View File

@@ -18,7 +18,10 @@ public class SharedFileAccessService
public Task<int> Register(IFileAccess fileAccess)
{
lock (FileAccesses)
{
if(!FileAccesses.Contains(fileAccess))
FileAccesses.Add(fileAccess);
}
return Task.FromResult(fileAccess.GetHashCode());
}
@@ -47,13 +50,13 @@ public class SharedFileAccessService
}
}
public async Task<string> GenerateUrl(IFileAccess fileAccess)
public async Task<string> GenerateToken(IFileAccess fileAccess)
{
var token = await JwtService.Create(data =>
{
data.Add("FileAccessId", fileAccess.GetHashCode().ToString());
}, "FileUpload", TimeSpan.FromMinutes(6));
}, "FileAccess", TimeSpan.FromMinutes(6));
return $"/api/upload?token={token}";
return token;
}
}

View File

@@ -64,7 +64,9 @@
if (firstRender)
{
await SharedFileAccessService.Register(FileAccess);
var url = await SharedFileAccessService.GenerateUrl(FileAccess);
var token = await SharedFileAccessService.GenerateToken(FileAccess);
var url = $"/api/upload?token={token}";
await DropzoneService.Create(DropzoneId, url);
@@ -74,7 +76,8 @@
{
await Task.Delay(TimeSpan.FromMinutes(5));
var newUrl = await SharedFileAccessService.GenerateUrl(FileAccess);
var newToken = await SharedFileAccessService.GenerateToken(FileAccess);
var newUrl = $"/api/upload?token={newToken}";
await DropzoneService.UpdateUrl(DropzoneId, newUrl);
}
});

View File

@@ -1,9 +1,14 @@
@using Moonlight.Features.FileManager.Models.Abstractions.FileAccess
@using MoonCoreUI.Services
@using MoonCore.Helpers
@using Moonlight.Features.FileManager.Services
@inject ToastService ToastService
@inject AlertService AlertService
@inject SharedFileAccessService SharedFileAccessService
@inject NavigationManager Navigation
@implements IDisposable
<LazyLoader @ref="LazyLoader" Load="Load">
<table class="w-100 table table-responsive table-row-bordered">
@@ -181,6 +186,9 @@
<li>
<a href="#" @onclick:preventDefault @onclick="() => Rename(entry)" class="dropdown-item">Rename</a>
</li>
<li>
<a href="#" @onclick:preventDefault @onclick="() => Download(entry)" class="dropdown-item">Download</a>
</li>
@if (OnMoveRequested != null)
{
<li>
@@ -267,6 +275,8 @@
}
}
#region Actions
private async Task Delete(params FileEntry[] entries)
{
if (entries.Length == 0)
@@ -311,6 +321,28 @@
await OnMoveRequested.Invoke(fileEntry);
}
private async Task Download(FileEntry fileEntry)
{
try
{
await SharedFileAccessService.Register(FileAccess);
var token = await SharedFileAccessService.GenerateToken(FileAccess);
var url = $"/api/download?token={token}&name={fileEntry.Name}";
await ToastService.Info("Starting download...");
Navigation.NavigateTo(url, true);
}
catch (Exception e)
{
Logger.Warn("Unable to start download");
Logger.Warn(e);
await ToastService.Danger("Failed to start download");
}
}
#endregion
#region Selection
private async Task HandleSelected(FileEntry fileEntry, ChangeEventArgs args)
@@ -387,4 +419,9 @@
#endregion
public async Task Refresh() => await LazyLoader.Reload();
public async void Dispose()
{
await SharedFileAccessService.Unregister(FileAccess);
}
}