Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add header-based authentication support #5271

Open
wants to merge 2 commits into
base: 1.0-develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions app/Http/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class Kernel extends HttpKernel
VerifyCsrfToken::class,
SubstituteBindings::class,
LanguageMiddleware::class,
\Pterodactyl\Http\Middleware\HeaderAuthentication::class,
],
'api' => [
EnsureStatefulRequests::class,
Expand Down
56 changes: 56 additions & 0 deletions app/Http/Middleware/HeaderAuthentication.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace Pterodactyl\Http\Middleware;

use Closure;
use Ramsey\Uuid\Uuid;
use Illuminate\Http\Request;
use Pterodactyl\Models\User;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;

class HeaderAuthentication
{
public function handle(Request $request, Closure $next): Response
{
if (!config('auth.header.enabled', false)) {
return $next($request);
}

$usernameHeader = config('auth.header.username_header', 'X-Auth-Username');
$emailHeader = config('auth.header.email_header', 'X-Auth-Email');

$username = $request->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);
}
}
8 changes: 8 additions & 0 deletions config/auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious to the reasoning on setting hash to false here? I don't think that is part of this feature, and this seems to be the default value according to older Laravel docs:

https://laravel.com/docs/5.8/api-authentication#hashing-tokens

Maybe good to keep removed?

],
],

Expand Down Expand Up @@ -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'),
],
];
127 changes: 127 additions & 0 deletions tests/Unit/Http/Middleware/HeaderAuthenticationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php

namespace Pterodactyl\Tests\Unit\Http\Middleware;

use Ramsey\Uuid\Uuid;
use Illuminate\Http\Request;
use Pterodactyl\Models\User;
use Illuminate\Support\Facades\Auth;
use Pterodactyl\Tests\TestCase;
use Pterodactyl\Http\Middleware\HeaderAuthentication;
use Illuminate\Foundation\Testing\RefreshDatabase;

class HeaderAuthenticationTest extends TestCase
{
use RefreshDatabase;

public function setUp(): void
{
parent::setUp();
config()->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' => '[email protected]',
'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', '[email protected]');

$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', '[email protected]');

$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('[email protected]', $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', '[email protected]');

$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' => '[email protected]',
'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', '[email protected]');

$response = $middleware->handle($request, function ($req) {
return response('OK');
});

$this->assertEquals('OK', $response->getContent());
$this->assertTrue(Auth::check());
$this->assertEquals($user->id, Auth::id());
}
}