filamentphp / filament

A collection of beautiful full-stack components for Laravel. The perfect starting point for your next app. Using Livewire, Alpine.js and Tailwind CSS.
https://filamentphp.com
MIT License
19.29k stars 2.96k forks source link

Uploaded files not retrievable due to server not acting as a proxy #9215

Closed ibvodafoneziggo closed 1 year ago

ibvodafoneziggo commented 1 year ago

Package

filament/forms

Package Version

2.17.53

Laravel Version

10.28.0

Livewire Version

2.12.6

PHP Version

8.2

Problem description

I'm uploading a file to S3 storage that is only accessible by my server, not to the client. When I'm uploading a file it works perfectly and the file is put in the S3 bucket. When previewing the file though, the frontend tries to read the S3 bucket itself, which only the server has access to.

Forms\Components\FileUpload::make('attachments') ->disk(config('storage.driver')) ->directory(config('storage.attachements-directory')) ->visibility(config('storage.storage-bucket-visibility')) ->maxSize(config('storage.max-upload-size')) ->maxFiles(config('storage.max-file-amount')) ->enableDownload() ->multiple()

This is the configuration I'm using, where the disk is an S3 bucket and the visibility is set to private. The URL that the frontend is trying to read is the disk configuration url.

Expected behavior

I'm expecting the server to act as a proxy to retrieve the file and serve it to the client, but that doesn't seem to be configurable.

Steps to reproduce

  1. Create a form with the fileUpload component.
  2. Set the disk as an S3 bucket only accessible by the server, and not the client / webbrowser.
  3. See the preview not load because the asset gives a 404.

Reproduction repository

https://github.com/ibvodafoneziggo/filament-uploadfile-issue-example

Relevant log output

No response

danharrin commented 1 year ago

When passing visibility('private'), Filament generates temporary URLs on the server side, is that not happening for you?

ibvodafoneziggo commented 1 year ago

It does generate the temporary URL, but the base url of that URL is still the inaccessible S3 url.

In the AWSS3 Adapter of Laravel I see this code for the temporary : https://github.com/laravel/framework/blob/cee91ab69478ab8a23b7ed2467a188bbbdb6f51b/src/Illuminate/Filesystem/AwsS3V3Adapter.php#L77C28-L77C28

I'll try to set the temporary_url to my own host to have url's generated as a proxy. If I'm correc this is the function that Filament uses if the driver is set to S3 (https://github.com/filamentphp/filament/blob/9d347a3a9a4dfba832de0b1f4fd2677280ec6c99/packages/forms/src/Components/BaseFileUpload.php#L125C34-L125C34).

ibvodafoneziggo commented 1 year ago

Adding a temporary_url config to S3 doesn't work, it's still using the AWS_URL/AWS_ENDPOINT to generate the URL.

Livewire docs are mentioning this however (something that breaks what I'm trying to achieve): If you've configured Livewire to use S3 for temporary file storage, calling ->temporaryUrl() will generate a temporary, signed URL from S3 directly so that you don't hit your Laravel app server for this preview at all. (https://laravel-livewire.com/docs/2.x/file-uploads#preview-urls)

danharrin commented 1 year ago

You can override getUploadedFileUsing() if you want, I don't think this is something that Filament can blanket fix for all people? We are generating temporary URLs using Laravel's own temporaryUrl() method.

danharrin commented 1 year ago

As above, I don't think there is a fix on the Filament side, we are just leaning on the existing file upload features of Livewire

nicko170 commented 1 year ago

Heya @ibvodafoneziggo

Hit the same issue today - We use minio purely as a proxied data store because it's easier than keeping volumes in k8s over multiple pods during deploys.

It's a very simple fix using the spaite media library plugin with the file uploads.

Force Livewire to use the local disk for temp uploads in config/livewire.php

return [
    'legacy_model_binding' => false,
    'temporary_file_upload' => [
        'disk' => 'local'
    ],
];

in config/media-library.php you'll want to override the Url generator

'url_generator' => \App\MediaLibrary\PrivateUrlGenerator::class,

Create the private url generator, that just returns a signed route

class PrivateUrlGenerator extends DefaultUrlGenerator
{
    public function getUrl(): string
    {
        return URL::signedRoute('download-media', ['media' => $this->media->id]);
    }
}

From that route, return the media

Route::get('/download-media/{media}', function (Media $media) {
    return $media;
})
    ->name('download-media')
    ->middleware('signed');

Media will be proxied via Laravel app now. Copy the same for the filament upload fields and you will be golden.

ibvodafoneziggo commented 1 year ago

Hi @nicko170, thanks for the neat solution! We went a little different route and opened up a public route on the S3 bucket. We then created a custom component that would retrieve the file via the public route.

brkfun commented 9 months ago

Heya @ibvodafoneziggo

Hit the same issue today - We use minio purely as a proxied data store because it's easier than keeping volumes in k8s over multiple pods during deploys.

It's a very simple fix using the spaite media library plugin with the file uploads.

Force Livewire to use the local disk for temp uploads in config/livewire.php

return [
    'legacy_model_binding' => false,
    'temporary_file_upload' => [
        'disk' => 'local'
    ],
];

in config/media-library.php you'll want to override the Url generator

'url_generator' => \App\MediaLibrary\PrivateUrlGenerator::class,

Create the private url generator, that just returns a signed route

class PrivateUrlGenerator extends DefaultUrlGenerator
{
    public function getUrl(): string
    {
        return URL::signedRoute('download-media', ['media' => $this->media->id]);
    }
}

From that route, return the media

Route::get('/download-media/{media}', function (Media $media) {
    return $media;
})
    ->name('download-media')
    ->middleware('signed');

Media will be proxied via Laravel app now. Copy the same for the filament upload fields and you will be golden.

this is a good solution to problem but want to improve this solution :

use Spatie\MediaLibrary\Support\UrlGenerator\DefaultUrlGenerator;

class PrivateS3UrlGenerator extends DefaultUrlGenerator
{
    public function getUrl(): string
    {
        if ($this->getDiskName() === 's3-private') {// s3 private for your private s3 bucket intended to have temp url.
            return $this->getTemporaryUrl(now()->addMinutes(5));
        }
        return parent::getUrl();// or any other disks goes to get link directly.
    }
}