In traditional file architectures, clients post massive binary payloads straight to the API server, which buffers bytes into memory before proxying to storage buckets. This creates fatal bottlenecks:
- API Bandwidth Saturation: Large uploads monopolize API memory and network bandwidth;
- Credential Exposure: Leaking long-lived root S3 AccessKeys to frontend applications;
- Insecure Direct Object References (IDOR): Guessing object keys allows unauthorized users to download private tenant files.
BitzOrcas.Modern implements Presigned Direct Uploads + Authoritative Metadata Isolation: The API issues short-lived presigned URLs, enabling clients to stream binaries directly to S3/MinIO while consuming zero API bandwidth.
Presigned Direct Upload and Download Architecture
Step 1: Issuing Secure Presigned Upload URLs
Use IFileStorage to generate scoped upload endpoints:
using BitzOrcas.Application.Abstractions.Storage;using BitzOrcas.Domain.Results;
public sealed class CreateUploadSessionCommandHandler( IFileStorage fileStorage, ICurrentTenant currentTenant){ public async ValueTask<Result<UploadSessionDto>> Handle( CreateUploadSessionCommand command, CancellationToken ct) { // ① Generate safe, namespaced ObjectKey with tenant isolation prefix var tenantId = currentTenant.Tenant.EffectiveTenantId; var safeObjectKey = $"tenants/{tenantId}/documents/{Guid.NewGuid():N}/{command.FileName}";
// ② Request 15-minute presigned PUT URL with strict payload bounds var presignedUrl = await fileStorage.GetPresignedUploadUrlAsync(new PresignedUploadRequest( ObjectKey: safeObjectKey, ContentType: command.ContentType, ExpiresIn: TimeSpan.FromMinutes(15), MaxSizeBytes: 50 * 1024 * 1024), ct); // Enforce 50MB ceiling
return Result<UploadSessionDto>.Success(new UploadSessionDto(safeObjectKey, presignedUrl)); }}Step 2: Authenticated Download Generation
Private buckets strictly forbid public read access. Downloads require short-lived GetObject URLs generated dynamically after authorization:
public async ValueTask<Result<string>> Handle(GetFileDownloadUrlQuery query, CancellationToken ct){ // 1. Verify caller permission against FileAsset in database var fileResult = await fileRepository.FindAsync(query.FileId, ct); if (fileResult.IsFailure) { return Result<string>.Failure(FileErrors.NotFound); }
var fileAsset = fileResult.GetValueOrThrow();
// 2. Dynamically issue 60-second read-only presigned download URL var downloadUrl = await fileStorage.GetPresignedDownloadUrlAsync(new PresignedDownloadRequest( ObjectKey: fileAsset.ObjectKey, ExpiresIn: TimeSpan.FromSeconds(60)), ct);
return Result<string>.Success(downloadUrl);}Step 3: Provider Adapters
| Adapter | Configuration | Target Scenario |
|---|---|---|
LocalFileStorage | Storage:Provider=Local | Local development, offline testing |
MinIOConnector | Storage:Provider=MinIO | Containerized development and private cloud |
S3CompatibleStore | Storage:Provider=S3 | AWS S3, Cloudflare R2, AliCloud OSS production |
Summary
IFileStorage provides scalable file handling:
- Zero API Bottleneck: Clients stream directly to object stores;
- Strict Tenant Boundaries: Private buckets + presigned URLs eliminate IDOR;
- Seamless Provider Switching: One code interface supports local disks and cloud S3 buckets.