Skip to content
bitzorcas
中EN

Reference

Object Storage and S3 Integration: IFileStorage Deep Dive

Explore the BitzOrcas.Modern object storage architecture. Learn how IFileStorage, MinIO/S3 adapters, presigned direct uploads, and tenant security eliminate server bandwidth bottlenecks.

Last updated

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:

  1. API Bandwidth Saturation: Large uploads monopolize API memory and network bandwidth;
  2. Credential Exposure: Leaking long-lived root S3 AccessKeys to frontend applications;
  3. 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

Database (FileAsset Entity)S3 / MinIO StorageAPI Host (Auth Guard)Database (FileAsset Entity)S3 / MinIO StorageAPI Host (Auth Guard)Verify permission -> Check quota -> Generate secure ObjectKeyBrowser Client1. POST /api/files/upload-session (Request upload credentials)12. Request short-lived PutObject Presigned URL23. Return Presigned URL (Valid 5 minutes)34. Return Presigned URL and SessionId45. PUT /bucket/object (Stream binary directly to S3)56. HTTP 200 (Upload Completed)67. POST /api/files/upload-session/finalize (Confirm upload)78. Persist FileAsset entity (Enforces tenant metadata)8Browser Client

Step 1: Issuing Secure Presigned Upload URLs

Use IFileStorage to generate scoped upload endpoints:

CreateUploadSessionCommandHandler.cs: Presigned Credential Issuance
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:

GetFileDownloadUrlQueryHandler.cs: Dynamic Download URL Generation
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

AdapterConfigurationTarget Scenario
LocalFileStorageStorage:Provider=LocalLocal development, offline testing
MinIOConnectorStorage:Provider=MinIOContainerized development and private cloud
S3CompatibleStoreStorage:Provider=S3AWS 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.

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%