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..dfdd95e5de --- /dev/null +++ b/app/Http/Middleware/HeaderAuthentication.php @@ -0,0 +1,56 @@ +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 = 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) { + Auth::login($user); + } + + return $next($request); + } +} \ No newline at end of file diff --git a/config/auth.php b/config/auth.php index 76db065714..13971d577d 100644 --- a/config/auth.php +++ b/config/auth.php @@ -59,6 +59,7 @@ 'api' => [ 'driver' => 'token', 'provider' => 'users', + 'hash' => false, ], ], @@ -126,4 +127,11 @@ */ 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), + + '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'), + ], ]; 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