diff --git a/DysonNetwork.Drive/DysonNetwork.Drive.csproj b/DysonNetwork.Drive/DysonNetwork.Drive.csproj
index f56863a..af7681b 100644
--- a/DysonNetwork.Drive/DysonNetwork.Drive.csproj
+++ b/DysonNetwork.Drive/DysonNetwork.Drive.csproj
@@ -53,7 +53,6 @@
-
diff --git a/DysonNetwork.Drive/Program.cs b/DysonNetwork.Drive/Program.cs
index 3cc28fa..f8897de 100644
--- a/DysonNetwork.Drive/Program.cs
+++ b/DysonNetwork.Drive/Program.cs
@@ -4,7 +4,6 @@ using DysonNetwork.Shared.Auth;
using DysonNetwork.Shared.Http;
using DysonNetwork.Shared.Registry;
using Microsoft.EntityFrameworkCore;
-using tusdotnet.Stores;
var builder = WebApplication.CreateBuilder(args);
@@ -42,8 +41,7 @@ using (var scope = app.Services.CreateScope())
await db.Database.MigrateAsync();
}
-var tusDiskStore = app.Services.GetRequiredService();
-app.ConfigureAppMiddleware(tusDiskStore);
+app.ConfigureAppMiddleware();
// Configure gRPC
app.ConfigureGrpcServices();
diff --git a/DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs b/DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs
index b343ddb..ab5dc93 100644
--- a/DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs
+++ b/DysonNetwork.Drive/Startup/ApplicationBuilderExtensions.cs
@@ -1,18 +1,14 @@
using DysonNetwork.Drive.Storage;
-using tusdotnet;
-using tusdotnet.Interfaces;
namespace DysonNetwork.Drive.Startup;
public static class ApplicationBuilderExtensions
{
- public static WebApplication ConfigureAppMiddleware(this WebApplication app, ITusStore tusStore)
+ public static WebApplication ConfigureAppMiddleware(this WebApplication app)
{
app.UseAuthorization();
app.MapControllers();
- app.MapTus("/api/tus", _ => Task.FromResult(TusService.BuildConfiguration(tusStore, app.Configuration)));
-
return app;
}
diff --git a/DysonNetwork.Drive/Storage/TusService.cs b/DysonNetwork.Drive/Storage/TusService.cs
deleted file mode 100644
index 0ab0d78..0000000
--- a/DysonNetwork.Drive/Storage/TusService.cs
+++ /dev/null
@@ -1,301 +0,0 @@
-using System.Net;
-using System.Text;
-using System.Text.Json;
-using DysonNetwork.Drive.Billing;
-using DysonNetwork.Shared.Auth;
-using DysonNetwork.Shared.Proto;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Options;
-using NodaTime;
-using tusdotnet.Interfaces;
-using tusdotnet.Models;
-using tusdotnet.Models.Configuration;
-
-namespace DysonNetwork.Drive.Storage;
-
-public abstract class TusService
-{
- public static DefaultTusConfiguration BuildConfiguration(
- ITusStore store,
- IConfiguration configuration
- ) => new()
- {
- Store = store,
- Events = new Events
- {
- OnAuthorizeAsync = async eventContext =>
- {
- if (eventContext.Intent == IntentType.DeleteFile)
- {
- eventContext.FailRequest(
- HttpStatusCode.BadRequest,
- "Deleting files from this endpoint was disabled, please refer to the Dyson Network File API."
- );
- return;
- }
-
- var httpContext = eventContext.HttpContext;
- if (httpContext.Items["CurrentUser"] is not Account currentUser)
- {
- eventContext.FailRequest(HttpStatusCode.Unauthorized);
- return;
- }
-
- if (eventContext.Intent != IntentType.CreateFile) return;
-
- using var scope = httpContext.RequestServices.CreateScope();
-
- if (!currentUser.IsSuperuser)
- {
- var pm = scope.ServiceProvider.GetRequiredService();
- var allowed = await pm.HasPermissionAsync(new HasPermissionRequest
- { Actor = $"user:{currentUser.Id}", Area = "global", Key = "files.create" });
- if (!allowed.HasPermission)
- eventContext.FailRequest(HttpStatusCode.Forbidden);
- }
-
- var filePool = httpContext.Request.Headers["X-FilePool"].FirstOrDefault();
- if (string.IsNullOrEmpty(filePool)) filePool = configuration["Storage:PreferredRemote"];
- if (!Guid.TryParse(filePool, out _))
- {
- eventContext.FailRequest(HttpStatusCode.BadRequest, "Invalid file pool id");
- return;
- }
-
- var fs = scope.ServiceProvider.GetRequiredService();
- var pool = await fs.GetPoolAsync(Guid.Parse(filePool!));
- if (pool is null)
- {
- eventContext.FailRequest(HttpStatusCode.BadRequest, "Pool not found");
- return;
- }
-
- if (pool.PolicyConfig.RequirePrivilege > 0)
- {
- if (currentUser.PerkSubscription is null)
- {
- eventContext.FailRequest(
- HttpStatusCode.Forbidden,
- $"You need to have join the Stellar Program to use this pool"
- );
- return;
- }
-
- var privilege =
- PerkSubscriptionPrivilege.GetPrivilegeFromIdentifier(currentUser.PerkSubscription.Identifier);
- if (privilege < pool.PolicyConfig.RequirePrivilege)
- {
- eventContext.FailRequest(
- HttpStatusCode.Forbidden,
- $"You need Stellar Program tier {pool.PolicyConfig.RequirePrivilege} to use this pool, you are tier {privilege}"
- );
- }
- }
-
- var bundleId = eventContext.HttpContext.Request.Headers["X-FileBundle"].FirstOrDefault();
- if (!string.IsNullOrEmpty(bundleId) && !Guid.TryParse(bundleId, out _))
- {
- eventContext.FailRequest(HttpStatusCode.BadRequest, "Invalid file bundle id");
- }
- },
- OnFileCompleteAsync = async eventContext =>
- {
- using var scope = eventContext.HttpContext.RequestServices.CreateScope();
- var services = scope.ServiceProvider;
-
- var httpContext = eventContext.HttpContext;
- if (httpContext.Items["CurrentUser"] is not Account user) return;
-
- var file = await eventContext.GetFileAsync();
- var metadata = await file.GetMetadataAsync(eventContext.CancellationToken);
- var fileName = metadata.TryGetValue("filename", out var fn)
- ? fn.GetString(Encoding.UTF8)
- : "uploaded_file";
- var contentType = metadata.TryGetValue("content-type", out var ct) ? ct.GetString(Encoding.UTF8) : null;
-
- var filePath = Path.Combine(configuration.GetValue("Tus:StorePath")!, file.Id);
-
- var filePool = httpContext.Request.Headers["X-FilePool"].FirstOrDefault();
- var bundleId = eventContext.HttpContext.Request.Headers["X-FileBundle"].FirstOrDefault();
- var encryptPassword = httpContext.Request.Headers["X-FilePass"].FirstOrDefault();
-
- if (string.IsNullOrEmpty(filePool))
- filePool = configuration["Storage:PreferredRemote"];
-
- Instant? expiredAt = null;
- var expiredString = httpContext.Request.Headers["X-FileExpire"].FirstOrDefault();
- if (!string.IsNullOrEmpty(expiredString) && int.TryParse(expiredString, out var expired))
- expiredAt = Instant.FromUnixTimeSeconds(expired);
-
- try
- {
- var fileService = services.GetRequiredService();
- var info = await fileService.ProcessNewFileAsync(
- user,
- file.Id,
- filePool!,
- bundleId,
- filePath,
- fileName,
- contentType,
- encryptPassword,
- expiredAt
- );
-
- using var finalScope = eventContext.HttpContext.RequestServices.CreateScope();
- var jsonOptions = finalScope.ServiceProvider.GetRequiredService>().Value
- .JsonSerializerOptions;
- var infoJson = JsonSerializer.Serialize(info, jsonOptions);
- eventContext.HttpContext.Response.Headers.Append("X-FileInfo", infoJson);
- }
- catch (Exception ex)
- {
- var logger = services.GetRequiredService>();
- eventContext.HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
- await eventContext.HttpContext.Response.WriteAsync(ex.Message);
- logger.LogError(ex, "Error handling file upload...");
- }
- },
- OnBeforeCreateAsync = async eventContext =>
- {
- var httpContext = eventContext.HttpContext;
- if (httpContext.Items["CurrentUser"] is not Account currentUser)
- {
- eventContext.FailRequest(HttpStatusCode.Unauthorized);
- return;
- }
- var accountId = Guid.Parse(currentUser.Id);
-
- var poolId = eventContext.HttpContext.Request.Headers["X-FilePool"].FirstOrDefault();
- if (string.IsNullOrEmpty(poolId)) poolId = configuration["Storage:PreferredRemote"];
- if (!Guid.TryParse(poolId, out _))
- {
- eventContext.FailRequest(HttpStatusCode.BadRequest, "Invalid file pool id");
- return;
- }
-
- var bundleId = eventContext.HttpContext.Request.Headers["X-FileBundle"].FirstOrDefault();
- if (!string.IsNullOrEmpty(bundleId) && !Guid.TryParse(bundleId, out _))
- {
- eventContext.FailRequest(HttpStatusCode.BadRequest, "Invalid file bundle id");
- return;
- }
-
- var metadata = eventContext.Metadata;
- var contentType = metadata.TryGetValue("content-type", out var ct) ? ct.GetString(Encoding.UTF8) : null;
-
- var scope = eventContext.HttpContext.RequestServices.CreateScope();
-
- var rejected = false;
-
- var fs = scope.ServiceProvider.GetRequiredService();
- var pool = await fs.GetPoolAsync(Guid.Parse(poolId!));
- if (pool is null)
- {
- eventContext.FailRequest(HttpStatusCode.BadRequest, "Pool not found");
- rejected = true;
- }
-
- var logger = scope.ServiceProvider.GetRequiredService>();
-
- // Do the policy check
- var policy = pool!.PolicyConfig;
- if (!rejected && !pool.PolicyConfig.AllowEncryption)
- {
- var encryptPassword = eventContext.HttpContext.Request.Headers["X-FilePass"].FirstOrDefault();
- if (!string.IsNullOrEmpty(encryptPassword))
- {
- eventContext.FailRequest(
- HttpStatusCode.Forbidden,
- "File encryption is not allowed in this pool"
- );
- rejected = true;
- }
- }
-
- if (!rejected && policy.AcceptTypes is not null)
- {
- if (string.IsNullOrEmpty(contentType))
- {
- eventContext.FailRequest(
- HttpStatusCode.BadRequest,
- "Content type is required by the pool's policy"
- );
- rejected = true;
- }
- else
- {
- var foundMatch = false;
- foreach (var acceptType in policy.AcceptTypes)
- {
- if (acceptType.EndsWith("/*", StringComparison.OrdinalIgnoreCase))
- {
- var type = acceptType[..^2];
- if (!contentType.StartsWith($"{type}/", StringComparison.OrdinalIgnoreCase)) continue;
- foundMatch = true;
- break;
- }
- else if (acceptType.Equals(contentType, StringComparison.OrdinalIgnoreCase))
- {
- foundMatch = true;
- break;
- }
- }
-
- if (!foundMatch)
- {
- eventContext.FailRequest(
- HttpStatusCode.Forbidden,
- $"Content type {contentType} is not allowed by the pool's policy"
- );
- rejected = true;
- }
- }
- }
-
- if (!rejected && policy.MaxFileSize is not null)
- {
- if (eventContext.UploadLength > policy.MaxFileSize)
- {
- eventContext.FailRequest(
- HttpStatusCode.Forbidden,
- $"File size {eventContext.UploadLength} is larger than the pool's maximum file size {policy.MaxFileSize}"
- );
- rejected = true;
- }
- }
-
- if (!rejected)
- {
- var quotaService = scope.ServiceProvider.GetRequiredService();
- var (ok, billableUnit, quota) = await quotaService.IsFileAcceptable(
- accountId,
- pool.BillingConfig.CostMultiplier ?? 1.0,
- eventContext.UploadLength
- );
- if (!ok)
- {
- eventContext.FailRequest(
- HttpStatusCode.Forbidden,
- $"File size {billableUnit} MiB is exceeded the user's quota {quota} MiB"
- );
- rejected = true;
- }
- }
-
- if (rejected)
- logger.LogInformation("File rejected #{FileId}", eventContext.FileId);
- },
- OnCreateCompleteAsync = eventContext =>
- {
- var directUpload = eventContext.HttpContext.Request.Headers["X-DirectUpload"].FirstOrDefault();
- if (!string.IsNullOrEmpty(directUpload)) return Task.CompletedTask;
-
- var gatewayUrl = configuration["GatewayUrl"];
- if (gatewayUrl is not null)
- eventContext.SetUploadUrl(new Uri(gatewayUrl + "/drive/tus/" + eventContext.FileId));
- return Task.CompletedTask;
- },
- }
- };
-}
\ No newline at end of file