misc_pterodactyl-panel/app/Http/Middleware/Api/AuthenticateKey.php
Dane Everitt 60eff40a0c
Fix session management on client API requests; closes #3727
Versions of Pterodactyl prior to 1.6.3 used a different throttle pathway for
requests. That pathway found the current request user before continuing on to
other in-app middleware, thus the user was available downstream.

Changes introduced in 1.6.3 changed the throttler logic, therefore removing this
step. As a result, the client API could not always get the currently authenticated
user when cookies were used (aka, requests from the Panel UI, and not API directly).

This change corrects the logic to get the session setup correctly before falling
through to authenticating as a user using the API key. If a cookie is present and a
user is found as a result that session will be used. If an API key is provided it is
ignored when a cookie is also present.

In order to keep the API stateless any session created for an API request stemming
from an API key will have the associated session deleted at the end of the request,
and the 'Set-Cookies' header will be stripped from the response.
2021-11-03 20:51:39 -07:00

108 lines
3.4 KiB
PHP

<?php
namespace Pterodactyl\Http\Middleware\Api;
use Closure;
use Carbon\CarbonImmutable;
use Illuminate\Http\Request;
use Pterodactyl\Models\User;
use Pterodactyl\Models\ApiKey;
use Illuminate\Auth\AuthManager;
use Illuminate\Contracts\Encryption\Encrypter;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class AuthenticateKey
{
/**
* @var \Illuminate\Auth\AuthManager
*/
private $auth;
/**
* @var \Illuminate\Contracts\Encryption\Encrypter
*/
private $encrypter;
/**
* @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface
*/
private $repository;
/**
* AuthenticateKey constructor.
*/
public function __construct(ApiKeyRepositoryInterface $repository, AuthManager $auth, Encrypter $encrypter)
{
$this->auth = $auth;
$this->encrypter = $encrypter;
$this->repository = $repository;
}
/**
* Handle an API request by verifying that the provided API key is in a valid
* format and exists in the database. If there is currently a user in the session
* do not even bother to look at the token (they provided a cookie for this to
* be the case).
*
* @return mixed
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function handle(Request $request, Closure $next, int $keyType)
{
if (is_null($request->bearerToken()) && is_null($request->user())) {
throw new HttpException(401, null, null, ['WWW-Authenticate' => 'Bearer']);
}
// This is a request coming through using cookies, we have an authenticated user
// not using an API key. Make some fake API key models and continue on through
// the process.
if ($request->user() instanceof User) {
$model = (new ApiKey())->forceFill([
'user_id' => $request->user()->id,
'key_type' => ApiKey::TYPE_ACCOUNT,
]);
} else {
$model = $this->authenticateApiKey($request->bearerToken(), $keyType);
$this->auth->guard()->loginUsingId($model->user_id);
}
$request->attributes->set('api_key', $model);
return $next($request);
}
/**
* Authenticate an API key.
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
protected function authenticateApiKey(string $key, int $keyType): ApiKey
{
$identifier = substr($key, 0, ApiKey::IDENTIFIER_LENGTH);
$token = substr($key, ApiKey::IDENTIFIER_LENGTH);
try {
$model = $this->repository->findFirstWhere([
['identifier', '=', $identifier],
['key_type', '=', $keyType],
]);
} catch (RecordNotFoundException $exception) {
throw new AccessDeniedHttpException();
}
if (!hash_equals($this->encrypter->decrypt($model->token), $token)) {
throw new AccessDeniedHttpException();
}
$this->repository->withoutFreshModel()->update($model->id, ['last_used_at' => CarbonImmutable::now()]);
return $model;
}
}