diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 2f96477..2a01c05 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -56,7 +56,8 @@ class Handler extends ExceptionHandler { $response = parent::render($request, $exception); - if (in_array($response->status(), [403, 404])) { + // Only return an Inertia-response when there are special Vue-templates (403 and 404) and when it isn't an API request. + if (in_array($response->status(), [403, 404]) && ! $request->routeIs('api.*')) { return app(HandleInertiaRequests::class) ->handle($request, fn () => inertia()->render('Errors/' . $response->status(), ['status' => $response->status()]) ->toResponse($request)); diff --git a/app/Helpers/Setting.php b/app/Helpers/Setting.php index 8fafdb9..50ac0f4 100644 --- a/app/Helpers/Setting.php +++ b/app/Helpers/Setting.php @@ -1,17 +1,19 @@ key($key) ], [ 'value' => Arr::first($key) @@ -20,6 +22,7 @@ if (!function_exists('setting')) { try { cache()->forget('core.settings'); } catch (Exception $e) { + // } return true; diff --git a/app/Http/Middleware/GlobalApiAuthenticated.php b/app/Http/Middleware/GlobalApiAuthenticated.php index 2307a41..2ac0edf 100644 --- a/app/Http/Middleware/GlobalApiAuthenticated.php +++ b/app/Http/Middleware/GlobalApiAuthenticated.php @@ -8,28 +8,27 @@ use App\Services\Ploi\Exceptions\Http\Unauthenticated; class GlobalApiAuthenticated { - /** - * Handle an incoming request. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @return mixed - */ - public function handle(Request $request, Closure $next) + public function handle(Request $request, Closure $next): mixed { - if (!$this->isAuthenticated($request)) { + abort_unless($this->hasApiEnabled(), 404); + + abort_unless($this->isAuthenticated($request), 403); + + if (! $this->isAuthenticated($request)) { throw new Unauthenticated('Unauthenticated for global access.'); } return $next($request); } + protected function hasApiEnabled(): bool + { + return setting('enable_api') && (bool) setting('api_token'); + } + protected function isAuthenticated(Request $request) { - return - setting('enable_api') && - setting('api_token') && - $request->bearerToken() && - $request->bearerToken() === decrypt(setting('api_token')); + return $request->bearerToken() + && $request->bearerToken() === decrypt(setting('api_token')); } } diff --git a/app/Http/Resources/Api/UserResource.php b/app/Http/Resources/Api/UserResource.php index b6467b0..03e486c 100644 --- a/app/Http/Resources/Api/UserResource.php +++ b/app/Http/Resources/Api/UserResource.php @@ -6,13 +6,7 @@ use Illuminate\Http\Resources\Json\JsonResource; class UserResource extends JsonResource { - /** - * Transform the resource into an array. - * - * @param \Illuminate\Http\Request $request - * @return array - */ - public function toArray($request) + public function toArray($request): array { return [ 'id' => $this->id, diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 5d9c727..0004815 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -3,6 +3,7 @@ namespace App\Providers; use App\Models\Setting; +use Exception; use Illuminate\Support\ServiceProvider; use Illuminate\Validation\Rules\Password; @@ -14,7 +15,7 @@ class AppServiceProvider extends ServiceProvider return $app['cache']->remember('core.settings', now()->addDay(), function () { try { return Setting::pluck('value', 'key')->toArray(); - } catch (\Exception $exception) { + } catch (Exception $exception) { return []; } }); diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 19cd207..1daec3b 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -2,11 +2,11 @@ namespace App\Providers; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Route; use Illuminate\Cache\RateLimiting\Limit; -use Illuminate\Support\Facades\RateLimiter; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\RateLimiter; +use Illuminate\Support\Facades\Route; class RouteServiceProvider extends ServiceProvider { @@ -36,12 +36,13 @@ class RouteServiceProvider extends ServiceProvider $this->configureRateLimiting(); $this->routes(function () { - if (setting('enable_api')) { - Route::prefix('api') - ->middleware('api') - ->namespace($this->namespace . '\Api') - ->group(base_path('routes/api.php')); - } + // The settings('enable_api') is now handled by the GlobalApiAuthenticated middleware, + // because the conditional inside this service makes testing very hard. + Route::prefix('api') + ->middleware(['api', 'global.api.authenticated']) + ->namespace($this->namespace . '\Api') + ->as('api.') + ->group(base_path('routes/api.php')); Route::middleware('web') ->namespace($this->namespace) diff --git a/routes/api.php b/routes/api.php index 5b6ed39..ee36d95 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,11 +1,8 @@ 'global.api.authenticated'], function () { - Route::group(['prefix' => 'users'], function () { - Route::get('/', 'UserController@index'); - Route::post('/', 'UserController@store'); - Route::get('{user}', 'UserController@show'); - }); -}); +Route::resource('users', UserController::class) + ->names('user') + ->only('index', 'store', 'show'); diff --git a/tests/Unit/Http/Middleware/GlobalApiAuthenticatedTest.php b/tests/Unit/Http/Middleware/GlobalApiAuthenticatedTest.php new file mode 100644 index 0000000..1cd1a67 --- /dev/null +++ b/tests/Unit/Http/Middleware/GlobalApiAuthenticatedTest.php @@ -0,0 +1,30 @@ +toBeNull(); + expect(setting('api_token'))->toBeNull(); + + get(route('api.user.index')) + ->assertNotFound(); + + setting(['enable_api' => true,]); + setting(['api_token' => encrypt('secret')]); + + App::forgetInstance('settings'); + + expect(setting('enable_api'))->toBeTrue(); + expect(setting('api_token'))->not->toBeNull(); + + get(route('api.user.index')) + ->assertForbidden(); + + get(route('api.user.index'), ['Authorization' => 'Bearer wrong-secret']) + ->assertForbidden(); + + get(route('api.user.index'), ['Authorization' => 'Bearer secret']) + ->assertOk(); +});