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:
@@ -2,21 +2,31 @@ package responses
|
||||
|
||||
// PresignUploadResponse is what /api/uploads/presign returns to the client.
|
||||
//
|
||||
// The client uses URL + Fields to build a multipart/form-data POST directly
|
||||
// to S3-compatible storage (B2). Once the upload completes, the client calls
|
||||
// the relevant entity-creation endpoint (POST /api/task-completions/, POST
|
||||
// /api/documents/) with `upload_ids: [Id]` to claim and attach the object.
|
||||
// Flow: the client makes one PUT request to URL with the raw object bytes
|
||||
// as the body and Headers as the request headers (verbatim — the signature
|
||||
// binds them). On success, the client passes ID back via upload_ids[] on
|
||||
// POST /api/task-completions/ or POST /api/documents/ to claim and attach
|
||||
// the object.
|
||||
//
|
||||
// We use PUT (not POST) because Backblaze B2's S3-compatible endpoint does
|
||||
// not implement the S3 POST Object form upload — it returns HTTP 501 on
|
||||
// every request style. PUT works against AWS S3, B2, and MinIO uniformly.
|
||||
type PresignUploadResponse struct {
|
||||
// ID is the pending_uploads.id the client passes back via upload_ids[].
|
||||
ID uint `json:"id"`
|
||||
|
||||
// URL is the storage endpoint to POST to (no query string).
|
||||
// URL is the signed PUT URL. Includes all auth as query parameters.
|
||||
URL string `json:"upload_url"`
|
||||
|
||||
// Fields are the form fields (policy, signature, key, etc.) that must be
|
||||
// submitted with the multipart form. The file part must be named "file"
|
||||
// and come last per S3 POST policy rules.
|
||||
Fields map[string]string `json:"fields"`
|
||||
// Method is always "PUT" — emitted explicitly so clients don't have to
|
||||
// hardcode it. Reserved for the rare case we ever offer alternative
|
||||
// upload mechanisms.
|
||||
Method string `json:"method"`
|
||||
|
||||
// Headers must be sent verbatim on the PUT request. Currently includes
|
||||
// Content-Type and Content-Length; both are signed, and B2 will reject
|
||||
// any PUT whose headers don't match.
|
||||
Headers map[string]string `json:"headers"`
|
||||
|
||||
// Key is the object key chosen by the server. Echoed for client logging
|
||||
// and debugging; the canonical reference is via ID.
|
||||
|
||||
Reference in New Issue
Block a user