Skip to content

Downloading log files from a remote host fails when the host uses auth #537

Open

Description

@imrelaur

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:

  1. The remote host builds download_url as an absolute URL to itself, because LogFile::downloadUrl() and LogFolder::downloadUrl() use route().
  2. The forwarded response passes that URL to the browser unchanged.
  3. DownloadLink.vue calls ${url}/request straight from the browser, with no host parameter 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

  1. 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'],
  2. 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')],
        ],
    ],
  3. Open A's Log Viewer and select the Remote host. The files list loads.

  4. 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:120 and src/LogFolder.php:116: downloadUrl() returns route('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 no host param. Other calls in resources/js/stores/files.js pass params: { host: this.hostQueryParam }.
  • routes/api.php:20: files/{fileIdentifier}/download/request is already in the ForwardRequestToHostMiddleware group, so it could be proxied if the frontend sent host.
  • routes/api.php:34: files/{fileIdentifier}/download is outside the forwarding group but still under api_middleware, so the signed URL needs the remote's auth.

Proposed fix

  1. Send host with the download request. DownloadLink should call the local instance's request-download route with params: { host: hostQueryParam }, the same way the other actions do. The existing middleware then forwards it with the configured auth.
  2. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions