From fc81ec1f7cf7627e81d25f3888ce86e1febaca52 Mon Sep 17 00:00:00 2001 From: MIKE Date: Sun, 5 Jan 2025 23:35:19 -0800 Subject: [PATCH 1/2] Add header-based authentication support --- .env.example | 6 +++ app/Http/Kernel.php | 1 + app/Http/Middleware/HeaderAuthentication.php | 48 ++++++++++++++++++++ config/auth.php | 12 +++-- 4 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 app/Http/Middleware/HeaderAuthentication.php diff --git a/.env.example b/.env.example index fc97db9590..773ec31735 100644 --- a/.env.example +++ b/.env.example @@ -42,3 +42,9 @@ MAIL_FROM_NAME="Pterodactyl Panel" # # @see: https://github.com/pterodactyl/panel/pull/3110 # MAIL_EHLO_DOMAIN=panel.example.com + +# Header Authentication Settings +AUTH_HEADER_ENABLED=false +AUTH_HEADER_USERNAME=X-Auth-Username +AUTH_HEADER_EMAIL=X-Auth-Email +AUTH_HEADER_AUTO_CREATE=false diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index b7085d9ed8..0db7b1ca41 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -60,6 +60,7 @@ class Kernel extends HttpKernel VerifyCsrfToken::class, SubstituteBindings::class, LanguageMiddleware::class, + \Pterodactyl\Http\Middleware\HeaderAuthentication::class, ], 'api' => [ EnsureStatefulRequests::class, diff --git a/app/Http/Middleware/HeaderAuthentication.php b/app/Http/Middleware/HeaderAuthentication.php new file mode 100644 index 0000000000..f48d63e9ba --- /dev/null +++ b/app/Http/Middleware/HeaderAuthentication.php @@ -0,0 +1,48 @@ +header($usernameHeader); + $email = $request->header($emailHeader); + + if (!$username || !$email) { + return $next($request); + } + + $user = User::where('email', $email)->first(); + + if (!$user && config('auth.header.auto_create', false)) { + $user = User::create([ + 'username' => $username, + 'email' => $email, + 'name_first' => $username, + 'name_last' => '', + 'password' => bcrypt(str_random(32)), + 'root_admin' => false, + ]); + } + + if ($user) { + Auth::login($user); + } + + return $next($request); + } +} \ No newline at end of file diff --git a/config/auth.php b/config/auth.php index 76db065714..ea63421b44 100644 --- a/config/auth.php +++ b/config/auth.php @@ -55,9 +55,8 @@ 'driver' => 'session', 'provider' => 'users', ], - - 'api' => [ - 'driver' => 'token', + 'sanctum' => [ + 'driver' => 'sanctum', 'provider' => 'users', ], ], @@ -126,4 +125,11 @@ */ 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), + + 'header' => [ + 'enabled' => env('AUTH_HEADER_ENABLED', false), + 'username_header' => env('AUTH_HEADER_USERNAME', 'X-Auth-Username'), + 'email_header' => env('AUTH_HEADER_EMAIL', 'X-Auth-Email'), + 'auto_create' => env('AUTH_HEADER_AUTO_CREATE', false), + ], ]; From df1e60ebbb5f34913aa2e6abd0d6127c1c93b39c Mon Sep 17 00:00:00 2001 From: Mike Shaffer Date: Mon, 6 Jan 2025 00:29:59 -0800 Subject: [PATCH 2/2] Add header-based authentication support This commit adds support for header-based authentication, allowing users to authenticate via HTTP headers. This is particularly useful for proxy authentication via SSO providers like Authelia or Authentik. Features:\n- New HeaderAuthentication middleware\n- Configurable header names for username and email\n- Optional automatic user creation\n- Comprehensive test suite\n- SQLite and MySQL compatibility The feature can be enabled via environment variables:\nAUTH_HEADER_ENABLED=true\nAUTH_HEADER_AUTO_CREATE=true\nAUTH_HEADER_USERNAME=X-Auth-Username\nAUTH_HEADER_EMAIL=X-Auth-Email --- app/Http/Middleware/HeaderAuthentication.php | 26 ++-- config/auth.php | 8 +- .../Middleware/HeaderAuthenticationTest.php | 127 ++++++++++++++++++ 3 files changed, 149 insertions(+), 12 deletions(-) create mode 100644 tests/Unit/Http/Middleware/HeaderAuthenticationTest.php diff --git a/app/Http/Middleware/HeaderAuthentication.php b/app/Http/Middleware/HeaderAuthentication.php index f48d63e9ba..dfdd95e5de 100644 --- a/app/Http/Middleware/HeaderAuthentication.php +++ b/app/Http/Middleware/HeaderAuthentication.php @@ -3,9 +3,10 @@ namespace Pterodactyl\Http\Middleware; use Closure; +use Ramsey\Uuid\Uuid; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; use Pterodactyl\Models\User; +use Illuminate\Support\Facades\Auth; use Symfony\Component\HttpFoundation\Response; class HeaderAuthentication @@ -29,14 +30,21 @@ public function handle(Request $request, Closure $next): Response $user = User::where('email', $email)->first(); if (!$user && config('auth.header.auto_create', false)) { - $user = User::create([ - 'username' => $username, - 'email' => $email, - 'name_first' => $username, - 'name_last' => '', - 'password' => bcrypt(str_random(32)), - 'root_admin' => false, - ]); + $user = new User(); + $user->uuid = Uuid::uuid4()->toString(); + $user->username = $username; + $user->email = $email; + $user->name_first = $username; + $user->name_last = $username; + $user->password = bcrypt(Uuid::uuid4()->toString()); + $user->language = config('app.locale', 'en'); + $user->root_admin = false; + $user->use_totp = false; + $user->totp_secret = null; + $user->external_id = ''; + $user->gravatar = true; + $user->totp_authenticated_at = null; + $user->save(); } if ($user) { diff --git a/config/auth.php b/config/auth.php index ea63421b44..13971d577d 100644 --- a/config/auth.php +++ b/config/auth.php @@ -55,9 +55,11 @@ 'driver' => 'session', 'provider' => 'users', ], - 'sanctum' => [ - 'driver' => 'sanctum', + + 'api' => [ + 'driver' => 'token', 'provider' => 'users', + 'hash' => false, ], ], @@ -128,8 +130,8 @@ 'header' => [ 'enabled' => env('AUTH_HEADER_ENABLED', false), + 'auto_create' => env('AUTH_HEADER_AUTO_CREATE', false), 'username_header' => env('AUTH_HEADER_USERNAME', 'X-Auth-Username'), 'email_header' => env('AUTH_HEADER_EMAIL', 'X-Auth-Email'), - 'auto_create' => env('AUTH_HEADER_AUTO_CREATE', false), ], ]; diff --git a/tests/Unit/Http/Middleware/HeaderAuthenticationTest.php b/tests/Unit/Http/Middleware/HeaderAuthenticationTest.php new file mode 100644 index 0000000000..68605504bb --- /dev/null +++ b/tests/Unit/Http/Middleware/HeaderAuthenticationTest.php @@ -0,0 +1,127 @@ +set('auth.header.enabled', true); + } + + public function test_middleware_does_nothing_when_disabled() + { + config()->set('auth.header.enabled', false); + + $middleware = new HeaderAuthentication(); + $request = new Request(); + + $response = $middleware->handle($request, function ($req) { + return response('OK'); + }); + + $this->assertEquals('OK', $response->getContent()); + $this->assertFalse(Auth::check()); + } + + public function test_middleware_authenticates_existing_user() + { + $user = User::factory()->create([ + 'username' => 'testuser', + 'email' => 'test@example.com', + 'name_first' => 'Test', + 'name_last' => 'User', + 'external_id' => '', + ]); + + $middleware = new HeaderAuthentication(); + $request = new Request(); + $request->headers->set('X-Auth-Username', 'testuser'); + $request->headers->set('X-Auth-Email', 'test@example.com'); + + $response = $middleware->handle($request, function ($req) { + return response('OK'); + }); + + $this->assertEquals('OK', $response->getContent()); + $this->assertTrue(Auth::check()); + $this->assertEquals($user->id, Auth::id()); + } + + public function test_middleware_creates_new_user_when_enabled() + { + config()->set('auth.header.auto_create', true); + + $middleware = new HeaderAuthentication(); + $request = new Request(); + $request->headers->set('X-Auth-Username', 'testuser'); + $request->headers->set('X-Auth-Email', 'test@example.com'); + + $response = $middleware->handle($request, function ($req) { + return response('OK'); + }); + + $this->assertEquals('OK', $response->getContent()); + $this->assertTrue(Auth::check()); + $user = Auth::user(); + $this->assertEquals('testuser', $user->username); + $this->assertEquals('test@example.com', $user->email); + $this->assertEquals('', $user->external_id); + $this->assertNotNull($user->uuid); + } + + public function test_middleware_does_not_create_user_when_auto_create_disabled() + { + config()->set('auth.header.auto_create', false); + + $middleware = new HeaderAuthentication(); + $request = new Request(); + $request->headers->set('X-Auth-Username', 'testuser'); + $request->headers->set('X-Auth-Email', 'test@example.com'); + + $response = $middleware->handle($request, function ($req) { + return response('OK'); + }); + + $this->assertEquals('OK', $response->getContent()); + $this->assertFalse(Auth::check()); + } + + public function test_middleware_uses_custom_header_names() + { + config()->set('auth.header.username_header', 'HTTP_X_USERNAME'); + config()->set('auth.header.email_header', 'HTTP_X_EMAIL'); + + $user = User::factory()->create([ + 'username' => 'testuser', + 'email' => 'test@example.com', + 'name_first' => 'Test', + 'name_last' => 'User', + 'external_id' => '', + ]); + + $middleware = new HeaderAuthentication(); + $request = new Request(); + $request->headers->set('HTTP_X_USERNAME', 'testuser'); + $request->headers->set('HTTP_X_EMAIL', 'test@example.com'); + + $response = $middleware->handle($request, function ($req) { + return response('OK'); + }); + + $this->assertEquals('OK', $response->getContent()); + $this->assertTrue(Auth::check()); + $this->assertEquals($user->id, Auth::id()); + } +} \ No newline at end of file