Parameters
- key: string
- contentType: string
- contentLength: number
Returns Promise<string>
Presigned PUT URL that expires in 15 minutes
Security:
- Content-Type is locked into the signature, preventing upload of wrong file types.
- Content-Length is NOT signed here because browsers treat it as a "forbidden"
request header and set it automatically from the body. Signing it would cause
R2 to reject every browser upload with a SignatureDoesNotMatch error.
Size enforcement is handled server-side in POST /api/uploads/[jobId]/complete
via a HEAD request that checks the object's actual byte count against the
declared fileSize (layer 2).
Generate presigned upload URL for direct browser uploads