Receives local uploads and serves stored objects.
For the S3 backend clients upload straight to the bucket via the presigned URL
and these endpoints are unused; for the local backend the upload ticket points
PUT /storage/upload here.
Why the key is signed
The key decides both where the object lands and, on the way back out, what
extension it carries. Deriving it from the query string made it
client-controlled: an authenticated user could PUT avatars/<own_id>/x.html
and have it served back from our own origin as text/html. So the key comes
from a token GamendWeb.Uploads signed when it issued the ticket, and
?key= is only ever a cross-check. Authorization happened at ticket time,
which is also what lets non-avatar prefixes (entity icons) upload here at all.
Summary
Functions
GET /storage/*key — serve a stored object (local backend).
PUT /storage/upload?key=...&token=... — authenticated raw-body upload (local backend).