fix(uploads): switch from S3 POST policy to presigned PUT
Backblaze B2's S3-compatible endpoint does not implement the S3 POST Object operation. It returns HTTP 501 to every POST regardless of URL style — both path-style (https://s3.<region>.backblazeb2.com/<bucket>/) and virtual-hosted-style (https://<bucket>.s3.<region>.backblazeb2.com/). Yesterday's BucketLookupDNS fix produced virtual-hosted URLs, which is correct for AWS but doesn't help here — B2 rejects POST on either form. Verified with `curl -X POST https://...backblazeb2.com/honeyDueProd/` returning 501 directly, with no signature involved. Replace minio-go's PresignedPostPolicy with PresignHeader + http.MethodPut. The signed URL now points at a single PUT endpoint, with Content-Type and Content-Length signed via headers — B2/S3/MinIO all accept it. Drop the min/max content-length range (we sign exactly one length now); post-upload size verification still happens in VerifyAndClaim via HEAD. Response shape: - URL (was: signed POST endpoint) → now: signed PUT URL - Fields → renamed to Headers; client sends them as request headers, not multipart form parts - Method (new): always "PUT", emitted explicitly so clients don't have to hardcode Companion KMP/iOS commits switch the client paths from multipart POST to single PUT. Existing builds in the field will need to be rebuilt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -164,18 +164,12 @@ func (s *UploadService) Presign(
|
||||
ext := extensionForContentType(contentType)
|
||||
key := fmt.Sprintf("uploads/%s/%d/%s%s", subdir, userID, uuid.New().String(), ext)
|
||||
|
||||
// Sign the POST policy. The slack window lets the client encode once;
|
||||
// the server still rejects anything materially different from claimed.
|
||||
minB := contentLength - UploadPresignSlackBytes
|
||||
if minB < 0 {
|
||||
minB = 0
|
||||
}
|
||||
maxB := contentLength + UploadPresignSlackBytes
|
||||
if maxB > UploadMaxBytes {
|
||||
maxB = UploadMaxBytes
|
||||
}
|
||||
|
||||
post, err := s.s3.PresignedPost(ctx, key, contentType, minB, maxB, UploadPresignTTL)
|
||||
// Sign a single-use PUT URL that binds Content-Type and Content-Length.
|
||||
// B2 does not implement S3's POST Object form upload (returns 501), so
|
||||
// we use PUT — which works uniformly against AWS S3, B2, and MinIO.
|
||||
// Size mismatch is caught later in VerifyAndClaim via HEAD; we don't
|
||||
// need a min/max range here because the signature pins exactly one size.
|
||||
put, err := s.s3.PresignedPut(ctx, key, contentType, contentLength, UploadPresignTTL)
|
||||
if err != nil {
|
||||
return nil, apperrors.Internal(fmt.Errorf("presign upload: %w", err))
|
||||
}
|
||||
@@ -195,8 +189,9 @@ func (s *UploadService) Presign(
|
||||
|
||||
return &responses.PresignUploadResponse{
|
||||
ID: row.ID,
|
||||
URL: post.URL,
|
||||
Fields: post.Fields,
|
||||
URL: put.URL,
|
||||
Headers: put.Headers,
|
||||
Method: "PUT",
|
||||
Key: key,
|
||||
ExpiresAt: expiresAt.Format(time.RFC3339),
|
||||
}, nil
|
||||
|
||||
Reference in New Issue
Block a user