Ensuring comprehensive file protection in user-restricted areas

I have a question regarding the protection of areas that are accessible only with user login.

When I offer downloads in these protected areas, the files (and link) remain visible in the download manager even after logging out. This means that the file can still be downloaded if another person gains access to the computer, which is particularly concerning when dealing with sensitive data.

My question is: Why the entire content folder of the protected page is not secured by default, but only the page content itself?

I am aware of the “Protecting files” guide in the Kirby cookbook, but unfortunately, this solution does not work for my project since I need to secure two additional folder levels. In my case, access from all child pages is then blocked.

I don’t see why this shouldn’t work for a page tree as well with some modifications?

@texnixe Thank you for your reply.

The folder structure is organized as follows:

content
└── kunden
    ├── apple
    │   ├── concept1
    │   ├── concept2
    │   └── concept3
    └── sony
        ├── project1
        └── project2

The template kunden redirects to the home page <?php go('/') ?>, so no content needs to be protected here yet.


I only changed the path from your cookbook:

[
	'pattern' => 'kunden/(:any)/(:all)',
	'action'  => function ($filename) {
		if (kirby()->user() &&
			($page = page('kunden')) &&
			$file = $page->files()->findBy('filename', $filename)) {
			return $file->download();
		}
		return site()->errorPage();
	},
],

After logging in, I can see the level of …

content
└── kunden
    ├── apple

… but no images are loaded here:
“Failed to load resource: the server responded with a status of 404 (Not Found).”


Access to the next level is completely denied:

content
└── kunden
    ├── apple
    │   ├── concept1

I now have the following code (which is certainly not built efficiently), but at least makes all pages accessible and blocks direct file access. However, the code blocks the creation of thumbnails. These are displayed in their original size on the frontend.

<?php

use Kirby\Cms\Response;

Kirby::plugin('cookbook/files-firewall', [
  'routes'       => [
    // Route for all subfolders in 'kunden', including deeper levels
    [
      'pattern' => 'kunden/(:all)',
      'action'  => function ($path) {
        // Create a page based on the path
        $page = page('kunden/' . $path);
        
        // Check if the path points to a page and a user is logged in
        if ($page && kirby()->user()) {
          return $page;
        }

        // Check if the last part of the path is a file
        $segments = explode('/', $path);
        $filename = array_pop($segments);
        $subfolder = implode('/', $segments);

        if (($page = page('kunden/' . $subfolder)) && 
            ($file = $page->files()->findBy('filename', $filename)) && 
            kirby()->user()) {
          
          // Instead of downloading the file, send it for display in the browser
          return new Response(
            file_get_contents($file->root()),
            $file->mime(),
            200
          );
        }

        return site()->errorPage();
      },
    ],
  ],
  'components'   => [
    'file::url' => function ($kirby, $file) {
      if ($file->template() === 'protected') {
        return $kirby->url() . '/' . $file->parent()->id() . '/' . $file->filename();
      }
      return $file->mediaUrl();
    },
    'file::version' => function ($kirby, $file, array $options = []) {

      static $original;

      // if the file is protected, return the original file
      if ($file->template() === 'protected') {
        return $file;
      }
      // if static $original is null, get the original component
      if ($original === null) {
          $original = $kirby->nativeComponent('file::version');
      }

      // and return it with the given options
      return $original($kirby, $file, $options);
    }
  ],
]);

Your solution is, of course, much more compact, but in my case I can’t get it to work.

Just FYI: If you use multiple placeholders, you need to add variables for each of them. Note that (:any) refers to the part after kunden until the next slash, while (:all) catches anything that comes after, so not sure if this makes sense.

Edit: Please reread the recipe carefully. Thumbs will make your files accessible in the media folder, that’s why the file version component prevents it.

I’ve already made some progress and can access all folder levels.

However, there is a problem: the images in the template are no longer converted using <?= $file->resize(600)->url() ?> and instead, the image is displayed in its original size.
If I right-click on the build and open it in a new browser window, it is downloaded instead.

If you like, I can send you a link to the customer portal.

<?php

use Kirby\Cms\Response;

Kirby::plugin('cookbook/files-firewall', [
  'routes' => [
    [
      'pattern' => 'kunden/(:any)/(:any)/(:all)',
      'action'  => function ($parent, $subfolder, $filename) {

        $page = page('kunden/' . $parent . '/' . $subfolder);
        $file = $page ? $page->file($filename) : null;

        if ($file && kirby()->user()) {

          return Response::download($file->root(), $file->filename());
        }

        return site()->errorPage();
      },
    ],
  ],
  'components' => [
    'file::url' => function ($kirby, $file) {
      if ($file->template() === 'protected') {

        return url($file->parent()->id() . '/' . $file->filename());
      }
      return $file->mediaUrl();
    },
    'file::version' => function ($kirby, $file, array $options = []) {
      static $original;

      if ($file->template() === 'protected') {
        return $file;
      }

      if ($original === null) {
        $original = $kirby->nativeComponent('file::version');
      }

      return $original($kirby, $file, $options);
    }
  ],
]);

I’ve only just read your addendum. For the sake of clarity: That’s why no preview images are generated in the media folder and it is therefore correct that the original file is displayed.
This is of course correct for download files. So is there no solution for preview images?

You could adapt the fileversion component to allow certain thumb sizes which may be publicly accessible. Or you create small preview images manually and assign a different template (these will then not be protected)

Thank you! That’s a good solution.
The problem only affects standard image formats.
For PDF and PSD, a different plug-in is used to create a preview image.
Office documents and ZIP files are not affected by the problem anyway.
At least I now understand the firewall plug-in much better, and I know the cause of my problem.