Repository navigation
Downloading log files from a remote host fails when the host uses auth #537
Description
Version: opcodesio/log-viewer v3.24.2, Laravel 12, PHP 8.4
Description
When a remote host is configured with authentication, as the multiple hosts docs recommend, browsing its logs works but downloading a file or folder from it never does.
Browsing goes through the local instance: the frontend sends ?host=<identifier>, and ForwardRequestToHostMiddleware forwards the request server-side with the configured token, basic auth or headers.
Downloads skip that proxy:
- The remote host builds
download_urlas an absolute URL to itself, becauseLogFile::downloadUrl()andLogFolder::downloadUrl()useroute(). - The forwarded response passes that URL to the browser unchanged.
DownloadLink.vuecalls${url}/requeststraight from the browser, with nohostparameter and none of the host's configured auth.
So the browser makes a cross-origin request to the remote host without credentials. It fails with a CORS error, or a 401 when CORS allows it. Even when /request succeeds, the signed download route is registered in the same api_middleware group, so opening the returned URL needs the remote's auth too.
Downloads from a remote host only work when the browser can reach it directly and is already authorised there.
Steps to reproduce
-
Set up two apps with Log Viewer. On the remote one (B), protect the API with a token, for example:
// B: config/log-viewer.php 'api_middleware' => ['auth:sanctum'],
-
On the local one (A), add B as a host:
// A: config/log-viewer.php 'hosts' => [ 'local' => ['name' => 'Local', 'host' => null], 'remote' => [ 'name' => 'Remote', 'host' => 'https://b.example.com/log-viewer', 'auth' => ['token' => env('LOG_VIEWER_REMOTE_TOKEN')], ], ],
-
Open A's Log Viewer and select the Remote host. The files list loads.
-
Click Download on any file.
Expected: the file downloads, the same way it does for the local host.
Actual: the browser sends GET https://b.example.com/log-viewer/api/files/<id>/download/request directly to B. It fails with a CORS error, or a 401 because the request has no token.
Relevant code
src/LogFile.php:120andsrc/LogFolder.php:116:downloadUrl()returnsroute('log-viewer.files.download', ...), an absolute URL on the host that generated it.resources/js/components/DownloadLink.vue:8:axios.get(`${props.url}/request`)has nohostparam. Other calls inresources/js/stores/files.jspassparams: { host: this.hostQueryParam }.routes/api.php:20:files/{fileIdentifier}/download/requestis already in theForwardRequestToHostMiddlewaregroup, so it could be proxied if the frontend senthost.routes/api.php:34:files/{fileIdentifier}/downloadis outside the forwarding group but still underapi_middleware, so the signed URL needs the remote's auth.
Proposed fix
- Send
hostwith the download request.DownloadLinkshould call the local instance'srequest-downloadroute withparams: { host: hostQueryParam }, the same way the other actions do. The existing middleware then forwards it with the configured auth. - Proxy the file too. For a remote host, the URL returned to the browser should point to a local route (carrying the host identifier and the remote's
expires/signature). That route fetches the remote's signed download URL server-side with the host's auth and streams the file back.
A workaround outside the package (what we run now) does both from the app. A middleware in api_middleware rewrites each remote download_url to a local route like /admin/log-viewer/hosts/{host}/{files|folders}/{identifier}/download. Two controllers then fetch the signed URL and stream the file with the host's token.
Related
- Download file not sending additional headers #298 fixed custom headers on downloads for the local host by switching to temporary signed URLs. That doesn't cover remote hosts.
Activity