javascriptlaravelthree.jslaravel-8laravel-storage

Getting access to non-public files from Laravel 8 storage folder with JS


I'm in the following scenario:

Now I'm trying to get access to these private STL files through javascript to load them in my Three.js scene for viewing.

My problem is: If I try to get the filepath from the database beforehand by using the storage_path() function in my Laravel controller and then pass this filepath (e.g. locally: /Users/Leon/MyLaravelApp/storage/app/users/1/file.stl) into my Three.js STLLoader function:

import * as THREE from 'three';
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js';

// {...}
let THREE_loader = new STLLoader();
let THREE_geometry;

THREE_loader.load(filePath, function (geometry) {
    THREE_geometry = geometry;

    // {...}
});

I - of course - get the error message 404 every time I try it, because the given path is relative to my web root and therefore not correct!

[Error] Failed to load resource: the server responded with a status of 404 (Not Found) http://127.0.0.1:8000/Users/Leon/MyLaravelApp/storage/app/users/1/file.stl

Now my question is:

Is there any possible way to get around this problem? How can i access private files (not in the public folder) from javascript in my Laravel 8 app? There has to be a solution to this right?


Other solutions I thought about:

But both of these "solutions" are definitely not one of my favorites!


Maybe someone could point me in the right direction? 🤔

Best regards, Leon


Solution

  • The solution I came up with over time:


    Generally:

    To get access to private files I figured that you must have some kind of special web route for that with some kind of FileController at the end which checks if you have permission to view the file and then decides to either abort() or otherwise return the file itself with a FileResponse.


    Laravel (my case):

    JS

    import * as THREE from 'three';
    import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js';
    
    let THREE_loader = new STLLoader();
    let THREE_geometry;
    
    THREE_loader.load('data/users/1/filename.stl', function (geometry) {
      THREE_geometry = geometry;
    
      // {...}
    });
    

    PHP (web route)

    <?php
    
    use App\Http\Controllers\File\FileController;
    use Illuminate\Support\Facades\Route;
    
    Route::get('data/users/{user_id}/{filename}', [FileController::class, 'load_user_stl']);
    

    PHP (FileController)

    <?php
    
    namespace App\Http\Controllers\File;
    
    use App\Http\Controllers\Controller;
    use Illuminate\Contracts\Filesystem\FileNotFoundException;
    use Illuminate\Support\Facades\Auth;
    use Illuminate\Support\Facades\File;
    use Illuminate\Support\Facades\Gate;
    use Illuminate\Support\Facades\Response;
    
    class FileController extends Controller
    {
        public function load_user_stl($user_id, $filename): \Illuminate\Http\Response
        {
            if (str_contains($filename, '.stl')) {
                if (Gate::allows('access-user-stl') || Auth::id() == $user_id) {
                    return $this->return_user_stl($user_id, $filename);
                } else {
                    abort(403);
                }
            } else {
                abort(400);
            }
        }
    
        public function return_user_stl($user_id, $filename): \Illuminate\Http\Response
        {
            $path = storage_path('app/data/users/'.$user_id.'/'.$filename);
    
            try {
                $file = File::get($path);
                $type = File::mimeType($path);
                $response = Response::make($file);
                $response->header('Content-Type', $type);
    
                return $response;
            } catch (FileNotFoundException) {
                abort(404);
            }
        }
    }
    

    Hope I could help you somehow and if somebody has an even cleaner way let me know:)

    Leon