Skip to main content

Authenticated media uploads

This API capability lets an authenticated user make a file durable media without giving the browser AWS credentials or exposing the S3 origin. It is implemented by the users slice, which establishes the application owner, and the media-library slice, which manages the file lifecycle and delivery.

Identity and ownership​

The shared API authentication boundary validates Supabase Auth access tokens (see Authentication) before a protected media operation can run. It passes only a verified Supabase Auth user identifier into the users slice. The slice then resolves that identifier to the application-owned users.id, creating the row safely on the first authenticated request. Media ownership always uses this internal UUID, not a client-supplied identity-provider identifier.

The health endpoint is deliberately public. Media operations are individually protected, so a request without a valid access token does not reach the media behavior.

File lifecycle​

A media upload is a two-phase workflow:

  1. The API accepts an upload request only when its purpose, declared MIME type, filename extension, and size satisfy that purpose's configured policy. It records a pending attempt with a generated, owner-scoped temporary key.
  2. The client submits the returned short-lived form directly to private S3. The signed POST binds the exact key, content type, upload identifier metadata, and a byte-length range. An optional SHA-256 checksum is also bound when the client supplied one.
  3. The owner asks the API to finalize the attempt. The API checks stored object metadata and length, reads only a bounded prefix to detect the actual file format, and rejects a mismatch. It copies verified bytes into a durable, deterministic media/ key and records the completed media item.

Finalization is retry-safe. A completed attempt returns its existing media item. If PostgreSQL did not commit after S3 copied the object, a later retry can find the deterministic durable key and finish the database work. Temporary-object deletion happens after the commit and is best-effort, so a lifecycle rule should remove any remaining temporary objects.

The current detector supports PDF, GIF, JPEG, PNG, and WebP bytes. A deployment can allow any subset of those formats per purpose; it cannot configure a format that the detector does not support. File-signature checking confirms the stored bytes match the declared supported format, but it is not malware scanning or content moderation.

Delivery authorization​

The media owner always receives delivery access. A non-owner is evaluated by an injected business-policy boundary; the shipped default denies access and returns no delivery URL. This lets a future feature such as workspace sharing or a purchase rule grant access without allowing the media slice to query that feature's tables directly.

After authorization, the API returns a short-lived signed CloudFront URL for the durable key. The URL is a bearer credential until it expires, so clients should not treat it as a permanent identifier or publish it. The database stores the object key rather than a delivery URL, allowing the distribution hostname to change without rewriting media records.

Deployment boundary​

The API requires a private S3 bucket, a CloudFront distribution configured to read that private origin, a trusted signing key group, upload CORS appropriate to the browser application, and lifecycle expiration for temporary objects. Those AWS resources and browser upload UI are deployment responsibilities, not part of this capability. The API validates its Supabase, S3, upload-policy, and CloudFront settings before it listens; see Environment variables for the required configuration.