Skip to content

Commit 683e4d6

Browse files
committed
feat!: add signed URLs, rename package Service Provider and Facade
1 parent b8d312b commit 683e4d6

11 files changed

+238
-32
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
"extra": {
8484
"laravel": {
8585
"providers": [
86-
"AceOfAces\\LaravelImageTransformUrl\\LaravelImageTransformUrlServiceProvider"
86+
"AceOfAces\\LaravelImageTransformUrl\\ImageTransformUrlServiceProvider"
8787
]
8888
}
8989
},

config/image-transform-url.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,24 @@
108108
'decay_seconds' => env('IMAGE_TRANSFORM_RATE_LIMIT_DECAY_SECONDS', 60),
109109
],
110110

111+
/*
112+
|--------------------------------------------------------------------------
113+
| Signed URLs
114+
|--------------------------------------------------------------------------
115+
|
116+
| Below you may configure signed URLs, which can be used to protect image
117+
| transformations from unauthorized access. Signature verification is
118+
| only applied to images from the for_source_directories array.
119+
|
120+
*/
121+
122+
'signed_urls' => [
123+
'enabled' => env('IMAGE_TRANSFORM_SIGNED_URLS_ENABLED', false),
124+
'for_source_directories' => env('IMAGE_TRANSFORM_SIGNED_URLS_FOR_SOURCE_DIRECTORIES', [
125+
//
126+
]),
127+
],
128+
111129
/*
112130
|--------------------------------------------------------------------------
113131
| Response Headers

routes/image.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
declare(strict_types=1);
44

55
use AceOfAces\LaravelImageTransformUrl\Http\Controllers\ImageTransformerController;
6+
use AceOfAces\LaravelImageTransformUrl\Http\Middleware\SignedImageTransformMiddleware;
67
use Illuminate\Support\Facades\Route;
78

89
Route::prefix(config()->string('image-transform-url.route_prefix'))->group(function () {
@@ -11,11 +12,13 @@
1112
->where('pathPrefix', '[a-zA-Z][a-zA-Z0-9_-]*')
1213
->where('options', '([a-zA-Z]+=-?[a-zA-Z0-9]+,?)+')
1314
->where('path', '.*\..*')
14-
->name('image.transform');
15+
->name('image.transform')
16+
->middleware(SignedImageTransformMiddleware::class);
1517

1618
// Default path prefix route
1719
Route::get('{options}/{path}', [ImageTransformerController::class, 'transformDefault'])
1820
->where('options', '([a-zA-Z]+=-?[a-zA-Z0-9]+,?)+')
1921
->where('path', '.*\..*')
20-
->name('image.transform.default');
22+
->name('image.transform.default')
23+
->middleware(SignedImageTransformMiddleware::class);
2124
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AceOfAces\LaravelImageTransformUrl\Exceptions;
6+
7+
use RuntimeException;
8+
use Throwable;
9+
10+
class InvalidConfigurationException extends RuntimeException
11+
{
12+
public function __construct(string $message, $code = 0, ?Throwable $previous = null)
13+
{
14+
parent::__construct($message, $code, $previous);
15+
}
16+
}

src/Facades/ImageTransformUrl.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AceOfAces\LaravelImageTransformUrl\Facades;
6+
7+
use Illuminate\Support\Facades\Facade;
8+
9+
/**
10+
* @see \AceOfAces\LaravelImageTransformUrl\LaravelImageTransformUrl;
11+
*
12+
* @method string signedUrl(string $path, array|string $options = [], ?string $pathPrefix = null, \DateTimeInterface|\DateInterval|int|null $expiration = null, ?bool $absolute = true)
13+
* @method string temporarySignedUrl(string $path, array|string $options = [], \DateTimeInterface|\DateInterval|int $expiration, ?string $pathPrefix = null, ?bool $absolute = true)
14+
*/
15+
class ImageTransformUrl extends Facade
16+
{
17+
protected static function getFacadeAccessor(): string
18+
{
19+
return \AceOfAces\LaravelImageTransformUrl\ImageTransformUrl::class;
20+
}
21+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AceOfAces\LaravelImageTransformUrl\Http\Middleware;
6+
7+
use Closure;
8+
use Illuminate\Http\Request;
9+
use Illuminate\Routing\Exceptions\InvalidSignatureException;
10+
use Illuminate\Routing\Middleware\ValidateSignature;
11+
use Symfony\Component\HttpFoundation\Response;
12+
13+
class SignedImageTransformMiddleware
14+
{
15+
/**
16+
* Handle an incoming request and conditionally apply signature verification.
17+
*
18+
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
19+
*/
20+
public function handle(Request $request, Closure $next): Response
21+
{
22+
$pathPrefix = $request->route('pathPrefix');
23+
24+
if (is_null($pathPrefix)) {
25+
$pathPrefix = config()->string('image-transform-url.default_source_directory');
26+
}
27+
28+
if ($this->requiresSignatureVerification($pathPrefix)) {
29+
return $this->validateSignature($request, $next);
30+
}
31+
32+
return $next($request);
33+
}
34+
35+
/**
36+
* Determine if signature verification is required for the given path prefix.
37+
*/
38+
protected function requiresSignatureVerification(string $pathPrefix): bool
39+
{
40+
if (! config()->boolean('image-transform-url.signed_urls.enabled')) {
41+
return false;
42+
}
43+
44+
$protectedDirectories = config()->array('image-transform-url.signed_urls.for_source_directories');
45+
46+
return in_array($pathPrefix, $protectedDirectories, true);
47+
}
48+
49+
/**
50+
* Validate the signature of the request.
51+
*
52+
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
53+
*/
54+
protected function validateSignature(Request $request, Closure $next): Response
55+
{
56+
$validator = new ValidateSignature;
57+
58+
try {
59+
return $validator->handle($request, $next);
60+
} catch (InvalidSignatureException $e) {
61+
throw $e;
62+
}
63+
}
64+
}

src/ImageTransformUrl.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AceOfAces\LaravelImageTransformUrl;
6+
7+
use AceOfAces\LaravelImageTransformUrl\Exceptions\InvalidConfigurationException;
8+
use DateInterval;
9+
use DateTimeInterface;
10+
use Illuminate\Support\Facades\URL;
11+
12+
class ImageTransformUrl
13+
{
14+
/**
15+
* Generate a signed URL for the image transformation.
16+
*
17+
* @param string $path The path to the image.
18+
* @param array $options The transformation options.
19+
* @param string|null $pathPrefix The path prefix to use. Defaults to the default path prefix.
20+
* @param DateTimeInterface|\DateInterval|int|null $expiration The expiration time for the signed URL.
21+
* @return string The signed URL.
22+
*
23+
* @throws InvalidConfigurationException If signed URLs are not enabled in the configuration.
24+
*/
25+
public function signedUrl(string $path, array|string $options = [], ?string $pathPrefix = null, DateTimeInterface|DateInterval|int|null $expiration = null, ?bool $absolute = true): string
26+
{
27+
if (! config()->boolean('image-transform-url.signed_urls.enabled')) {
28+
throw new InvalidConfigurationException('Signed URLs are not enabled. Please check your configuration.');
29+
}
30+
31+
if (is_array($options)) {
32+
$options = collect($options)
33+
->map(fn ($value, $key) => "$key=$value")
34+
->implode(',');
35+
}
36+
37+
if (empty($pathPrefix)) {
38+
return URL::signedRoute(
39+
'image.transform.default',
40+
['options' => $options, 'path' => $path],
41+
$expiration,
42+
$absolute
43+
);
44+
}
45+
46+
return URL::signedRoute(
47+
'image.transform',
48+
['pathPrefix' => $pathPrefix, 'options' => $options, 'path' => $path],
49+
$expiration,
50+
$absolute
51+
);
52+
}
53+
54+
/**
55+
* Create a temporary signed URL for the image transformation.
56+
*
57+
* @param string $path The path to the image.
58+
* @param array|string $options The transformation options.
59+
* @param DateTimeInterface|DateInterval|int $expiration The expiration time for the signed URL.
60+
* @param string|null $pathPrefix The path prefix to use. Defaults to the default path prefix.
61+
* @param bool|null $absolute Whether the URL should be absolute. Defaults to true.
62+
* @return string The temporary signed URL.
63+
*
64+
* @throws InvalidConfigurationException If signed URLs are not enabled in the configuration.
65+
*/
66+
public function temporarySignedUrl(string $path, array|string $options, DateTimeInterface|DateInterval|int $expiration, ?string $pathPrefix, ?bool $absolute = true): string
67+
{
68+
return $this->signedUrl($path, $options, $pathPrefix, $expiration, $absolute);
69+
}
70+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AceOfAces\LaravelImageTransformUrl;
6+
7+
use AceOfAces\LaravelImageTransformUrl\Http\Middleware\SignedImageTransformMiddleware;
8+
use Illuminate\Routing\Router;
9+
use Spatie\LaravelPackageTools\Package;
10+
use Spatie\LaravelPackageTools\PackageServiceProvider;
11+
12+
class ImageTransformUrlServiceProvider extends PackageServiceProvider
13+
{
14+
public function configurePackage(Package $package): void
15+
{
16+
/*
17+
* This class is a Package Service Provider
18+
*
19+
* More info: https://github.com/spatie/laravel-package-tools
20+
*/
21+
$package
22+
->name('laravel-image-transform-url')
23+
->hasConfigFile()
24+
->hasRoute('image');
25+
}
26+
27+
public function packageBooted(): void
28+
{
29+
$this->registerMiddleware();
30+
}
31+
32+
/**
33+
* Register the custom middleware.
34+
*/
35+
protected function registerMiddleware(): void
36+
{
37+
$router = $this->app->make(Router::class);
38+
39+
$router->aliasMiddleware('signed-image-transform', SignedImageTransformMiddleware::class);
40+
}
41+
}

src/LaravelImageTransformUrl.php

Lines changed: 0 additions & 5 deletions
This file was deleted.

src/LaravelImageTransformUrlServiceProvider.php

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)