A file of the store sent through the app: it checks that the person may
see it, then streams it from the store, a part at a time, so that the
bucket's address never leaves the app and a CDN in front keeps what it
may. The sibling of Storehouse.Redirect, which sends the person to a
signed URL instead.
In the router, in a scope that has the person:
forward "/files", Storehouse.Proxy, authorize: &MyAppWeb.Files.allowed?/2/files/2026/10/0b6…-board.png then sends the file. The function takes
the conn and the key; a key it refuses, a key that is not valid, or a
file that is not there, gives 404, so that no one learns which files
exist.
Responses
- The file's type is the store's, through the allow-list of
Storehouse.Check.content_type/1: another is sent asapplication/octet-stream. WithX-Content-Type-Options: nosniffandContent-Security-Policy: default-src 'none'; sandbox, andContent-Disposition: attachmentfor every type a browser should not show, asStorehouse.Local.Plugsends them. - A single
Rangegives206with that part, as forStorehouse.Local.Plug;Accept-Ranges: bytesalways. ETag(from the file's SHA-256, where the store keeps it) andLast-Modified(where the store says): a request withIf-None-MatchorIf-Modified-Sincethat still holds gives304, and no byte is read.
Options
:authorize- the function (required).truelets everyone in: a public bucket kept behind the app.:cache_control- theCache-Controlheader. The default is"private, max-age=0, must-revalidate": a file only some may see is never kept by a CDN for others. For files everyone may see,"public, max-age=31536000, immutable".:cache_tag-truesends the file'sCache-Tag(Storehouse.Purge.cache_tag/1).:chunk_bytes- the size of each read from the store. The default is 4 MiB.:adapter- the store. It must read files a part at a time (Storehouse.Adapter.read_range/3):Storehouse.LocalandStorehouse.S3do. The default isStorehouse.adapter/0.