Implemented download in the file manager. Made file access jwt more modular
This commit is contained in:
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,7 +45,7 @@ public class UploadController : Controller
|
|||||||
return BadRequest("Too many files sent");
|
return BadRequest("Too many files sent");
|
||||||
|
|
||||||
// Validate request
|
// Validate request
|
||||||
if (!await JwtService.Validate(uploadToken, "FileUpload"))
|
if (!await JwtService.Validate(uploadToken, "FileAccess"))
|
||||||
return StatusCode(403);
|
return StatusCode(403);
|
||||||
|
|
||||||
var uploadContext = await JwtService.Decode(uploadToken);
|
var uploadContext = await JwtService.Decode(uploadToken);
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ public class SharedFileAccessService
|
|||||||
public Task<int> Register(IFileAccess fileAccess)
|
public Task<int> Register(IFileAccess fileAccess)
|
||||||
{
|
{
|
||||||
lock (FileAccesses)
|
lock (FileAccesses)
|
||||||
FileAccesses.Add(fileAccess);
|
{
|
||||||
|
if(!FileAccesses.Contains(fileAccess))
|
||||||
|
FileAccesses.Add(fileAccess);
|
||||||
|
}
|
||||||
|
|
||||||
return Task.FromResult(fileAccess.GetHashCode());
|
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 =>
|
var token = await JwtService.Create(data =>
|
||||||
{
|
{
|
||||||
data.Add("FileAccessId", fileAccess.GetHashCode().ToString());
|
data.Add("FileAccessId", fileAccess.GetHashCode().ToString());
|
||||||
}, "FileUpload", TimeSpan.FromMinutes(6));
|
}, "FileAccess", TimeSpan.FromMinutes(6));
|
||||||
|
|
||||||
return $"/api/upload?token={token}";
|
return token;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,9 @@
|
|||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
await SharedFileAccessService.Register(FileAccess);
|
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);
|
await DropzoneService.Create(DropzoneId, url);
|
||||||
|
|
||||||
@@ -74,7 +76,8 @@
|
|||||||
{
|
{
|
||||||
await Task.Delay(TimeSpan.FromMinutes(5));
|
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);
|
await DropzoneService.UpdateUrl(DropzoneId, newUrl);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
@using Moonlight.Features.FileManager.Models.Abstractions.FileAccess
|
@using Moonlight.Features.FileManager.Models.Abstractions.FileAccess
|
||||||
@using MoonCoreUI.Services
|
@using MoonCoreUI.Services
|
||||||
@using MoonCore.Helpers
|
@using MoonCore.Helpers
|
||||||
|
@using Moonlight.Features.FileManager.Services
|
||||||
|
|
||||||
@inject ToastService ToastService
|
@inject ToastService ToastService
|
||||||
@inject AlertService AlertService
|
@inject AlertService AlertService
|
||||||
|
@inject SharedFileAccessService SharedFileAccessService
|
||||||
|
@inject NavigationManager Navigation
|
||||||
|
|
||||||
|
@implements IDisposable
|
||||||
|
|
||||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
<table class="w-100 table table-responsive table-row-bordered">
|
<table class="w-100 table table-responsive table-row-bordered">
|
||||||
@@ -181,6 +186,9 @@
|
|||||||
<li>
|
<li>
|
||||||
<a href="#" @onclick:preventDefault @onclick="() => Rename(entry)" class="dropdown-item">Rename</a>
|
<a href="#" @onclick:preventDefault @onclick="() => Rename(entry)" class="dropdown-item">Rename</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#" @onclick:preventDefault @onclick="() => Download(entry)" class="dropdown-item">Download</a>
|
||||||
|
</li>
|
||||||
@if (OnMoveRequested != null)
|
@if (OnMoveRequested != null)
|
||||||
{
|
{
|
||||||
<li>
|
<li>
|
||||||
@@ -267,6 +275,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Actions
|
||||||
|
|
||||||
private async Task Delete(params FileEntry[] entries)
|
private async Task Delete(params FileEntry[] entries)
|
||||||
{
|
{
|
||||||
if (entries.Length == 0)
|
if (entries.Length == 0)
|
||||||
@@ -295,7 +305,7 @@
|
|||||||
{
|
{
|
||||||
var name = await AlertService.Text($"Rename '{fileEntry.Name}'", "", fileEntry.Name);
|
var name = await AlertService.Text($"Rename '{fileEntry.Name}'", "", fileEntry.Name);
|
||||||
|
|
||||||
if(string.IsNullOrEmpty(name))
|
if (string.IsNullOrEmpty(name))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await FileAccess.Move(fileEntry.Name, name);
|
await FileAccess.Move(fileEntry.Name, name);
|
||||||
@@ -305,12 +315,34 @@
|
|||||||
|
|
||||||
private async Task RequestMove(FileEntry fileEntry)
|
private async Task RequestMove(FileEntry fileEntry)
|
||||||
{
|
{
|
||||||
if(OnMoveRequested == null)
|
if (OnMoveRequested == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await OnMoveRequested.Invoke(fileEntry);
|
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
|
#region Selection
|
||||||
|
|
||||||
private async Task HandleSelected(FileEntry fileEntry, ChangeEventArgs args)
|
private async Task HandleSelected(FileEntry fileEntry, ChangeEventArgs args)
|
||||||
@@ -387,4 +419,9 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public async Task Refresh() => await LazyLoader.Reload();
|
public async Task Refresh() => await LazyLoader.Reload();
|
||||||
|
|
||||||
|
public async void Dispose()
|
||||||
|
{
|
||||||
|
await SharedFileAccessService.Unregister(FileAccess);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user