From 4efc87559a8d215fd0617425c297cd5d19ae4209 Mon Sep 17 00:00:00 2001 From: Trey t Date: Wed, 6 May 2026 13:34:05 -0500 Subject: [PATCH] fix(uploads): force virtual-hosted-style URLs for B2 presigned POST MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backblaze B2's S3-compatible endpoint only implements POST Object on virtual-hosted-style URLs (https://.s3..backblazeb2.com/). Path-style POST returns HTTP 501 Not Implemented. minio-go's BucketLookupAuto only flips to virtual-hosted for AWS, Google, and Aliyun endpoints — for B2 it falls through to path-style, which is why every PresignedPostPolicy() call has been handing the mobile clients a URL that B2 then refuses with 501. Force BucketLookupDNS only when the endpoint is backblazeb2.com so MinIO dev (no DNS for arbitrary buckets at minio:9000) keeps its path-style default. Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/services/storage_backend_s3.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/internal/services/storage_backend_s3.go b/internal/services/storage_backend_s3.go index 264a3bc..4e77b71 100644 --- a/internal/services/storage_backend_s3.go +++ b/internal/services/storage_backend_s3.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/url" + "strings" "time" "github.com/minio/minio-go/v7" @@ -25,11 +26,22 @@ func NewS3Backend(endpoint, keyID, appKey, bucket string, useSSL bool, region st region = "us-east-1" } - client, err := minio.New(endpoint, &minio.Options{ + opts := &minio.Options{ Creds: credentials.NewStaticV4(keyID, appKey, ""), Secure: useSSL, Region: region, - }) + } + // B2's S3-compatible endpoint only implements POST Object on + // virtual-hosted-style URLs (https://.s3..backblazeb2.com/). + // Path-style POST returns HTTP 501. minio-go's auto-detection falls back + // to path-style for non-AWS endpoints, which breaks PresignedPostPolicy + // against B2. Force DNS lookup for B2 only — leave MinIO dev (no DNS for + // arbitrary buckets at minio:9000) on the path-style default. + if strings.Contains(endpoint, "backblazeb2.com") { + opts.BucketLookup = minio.BucketLookupDNS + } + + client, err := minio.New(endpoint, opts) if err != nil { return nil, fmt.Errorf("failed to create S3 client: %w", err) }