Initial pass at implementing Laravel Sanctum for authorization on the API
This commit is contained in:
parent
e313dff674
commit
bd37978a98
13 changed files with 324 additions and 220 deletions
23
app/Extensions/Laravel/Sanctum/NewAccessToken.php
Normal file
23
app/Extensions/Laravel/Sanctum/NewAccessToken.php
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Extensions\Laravel\Sanctum;
|
||||||
|
|
||||||
|
use Pterodactyl\Models\ApiKey;
|
||||||
|
use Laravel\Sanctum\NewAccessToken as SanctumAccessToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property \Pterodactyl\Models\ApiKey $accessToken
|
||||||
|
*/
|
||||||
|
class NewAccessToken extends SanctumAccessToken
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* NewAccessToken constructor.
|
||||||
|
*
|
||||||
|
* @noinspection PhpMissingParentConstructorInspection
|
||||||
|
*/
|
||||||
|
public function __construct(ApiKey $accessToken, string $plainTextToken)
|
||||||
|
{
|
||||||
|
$this->accessToken = $accessToken;
|
||||||
|
$this->plainTextToken = $plainTextToken;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Http;
|
namespace Pterodactyl\Http;
|
||||||
|
|
||||||
use Pterodactyl\Models\ApiKey;
|
|
||||||
use Illuminate\Auth\Middleware\Authorize;
|
use Illuminate\Auth\Middleware\Authorize;
|
||||||
use Illuminate\Auth\Middleware\Authenticate;
|
use Illuminate\Auth\Middleware\Authenticate;
|
||||||
use Illuminate\Http\Middleware\TrustProxies;
|
use Illuminate\Http\Middleware\TrustProxies;
|
||||||
|
@ -16,7 +15,6 @@ use Pterodactyl\Http\Middleware\AdminAuthenticate;
|
||||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||||
use Pterodactyl\Http\Middleware\LanguageMiddleware;
|
use Pterodactyl\Http\Middleware\LanguageMiddleware;
|
||||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||||
use Pterodactyl\Http\Middleware\Api\AuthenticateKey;
|
|
||||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||||
use Illuminate\Session\Middleware\AuthenticateSession;
|
use Illuminate\Session\Middleware\AuthenticateSession;
|
||||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||||
|
@ -25,13 +23,13 @@ use Pterodactyl\Http\Middleware\RedirectIfAuthenticated;
|
||||||
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
|
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
|
||||||
use Pterodactyl\Http\Middleware\Api\AuthenticateIPAccess;
|
use Pterodactyl\Http\Middleware\Api\AuthenticateIPAccess;
|
||||||
use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
|
use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
|
||||||
use Pterodactyl\Http\Middleware\Api\HandleStatelessRequest;
|
|
||||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||||
use Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate;
|
use Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate;
|
||||||
use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication;
|
use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication;
|
||||||
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode;
|
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode;
|
||||||
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
|
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
|
||||||
use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientBindings;
|
use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientBindings;
|
||||||
|
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
|
||||||
use Pterodactyl\Http\Middleware\Api\Application\AuthenticateApplicationUser;
|
use Pterodactyl\Http\Middleware\Api\Application\AuthenticateApplicationUser;
|
||||||
|
|
||||||
class Kernel extends HttpKernel
|
class Kernel extends HttpKernel
|
||||||
|
@ -67,29 +65,19 @@ class Kernel extends HttpKernel
|
||||||
RequireTwoFactorAuthentication::class,
|
RequireTwoFactorAuthentication::class,
|
||||||
],
|
],
|
||||||
'api' => [
|
'api' => [
|
||||||
HandleStatelessRequest::class,
|
|
||||||
IsValidJson::class,
|
IsValidJson::class,
|
||||||
StartSession::class,
|
EnsureFrontendRequestsAreStateful::class,
|
||||||
AuthenticateSession::class,
|
'auth:sanctum',
|
||||||
VerifyCsrfToken::class,
|
RequireTwoFactorAuthentication::class,
|
||||||
|
AuthenticateIPAccess::class,
|
||||||
],
|
],
|
||||||
'application-api' => [
|
'application-api' => [
|
||||||
SubstituteBindings::class,
|
SubstituteBindings::class,
|
||||||
'api..key:' . ApiKey::TYPE_APPLICATION,
|
|
||||||
AuthenticateApplicationUser::class,
|
AuthenticateApplicationUser::class,
|
||||||
AuthenticateIPAccess::class,
|
|
||||||
],
|
|
||||||
'client-api' => [
|
|
||||||
SubstituteClientBindings::class,
|
|
||||||
'api..key:' . ApiKey::TYPE_ACCOUNT,
|
|
||||||
AuthenticateIPAccess::class,
|
|
||||||
// This is perhaps a little backwards with the Client API, but logically you'd be unable
|
|
||||||
// to create/get an API key without first enabling 2FA on the account, so I suppose in the
|
|
||||||
// end it makes sense.
|
|
||||||
//
|
|
||||||
// You just wouldn't be authenticating with the API by providing a 2FA token.
|
|
||||||
RequireTwoFactorAuthentication::class,
|
|
||||||
],
|
],
|
||||||
|
// TODO: don't allow an application key to use the client API, but do allow a client
|
||||||
|
// api key to access the application API.
|
||||||
|
'client-api' => [SubstituteClientBindings::class],
|
||||||
'daemon' => [
|
'daemon' => [
|
||||||
SubstituteBindings::class,
|
SubstituteBindings::class,
|
||||||
DaemonAuthenticate::class,
|
DaemonAuthenticate::class,
|
||||||
|
@ -112,7 +100,5 @@ class Kernel extends HttpKernel
|
||||||
'bindings' => SubstituteBindings::class,
|
'bindings' => SubstituteBindings::class,
|
||||||
'recaptcha' => VerifyReCaptcha::class,
|
'recaptcha' => VerifyReCaptcha::class,
|
||||||
'node.maintenance' => MaintenanceMiddleware::class,
|
'node.maintenance' => MaintenanceMiddleware::class,
|
||||||
// API Specific Middleware
|
|
||||||
'api..key' => AuthenticateKey::class,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use Closure;
|
||||||
use IPTools\IP;
|
use IPTools\IP;
|
||||||
use IPTools\Range;
|
use IPTools\Range;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Laravel\Sanctum\TransientToken;
|
||||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
|
||||||
class AuthenticateIPAccess
|
class AuthenticateIPAccess
|
||||||
|
@ -20,14 +21,19 @@ class AuthenticateIPAccess
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next)
|
public function handle(Request $request, Closure $next)
|
||||||
{
|
{
|
||||||
$model = $request->attributes->get('api_key');
|
/** @var \Laravel\Sanctum\TransientToken|\Pterodactyl\Models\ApiKey $token */
|
||||||
|
$token = $request->user()->currentAccessToken();
|
||||||
|
|
||||||
if (is_null($model->allowed_ips) || empty($model->allowed_ips)) {
|
// If this is a stateful request just push the request through to the next
|
||||||
|
// middleware in the stack, there is nothing we need to explicitly check. If
|
||||||
|
// this is a valid API Key, but there is no allowed IP restriction, also pass
|
||||||
|
// the request through.
|
||||||
|
if ($token instanceof TransientToken || empty($token->allowed_ips)) {
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
$find = new IP($request->ip());
|
$find = new IP($request->ip());
|
||||||
foreach ($model->allowed_ips as $ip) {
|
foreach ($token->allowed_ips as $ip) {
|
||||||
if (Range::parse($ip)->contains($find)) {
|
if (Range::parse($ip)->contains($find)) {
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,109 +0,0 @@
|
||||||
<?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\Support\Facades\Session;
|
|
||||||
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, 'A bearer token or valid user session cookie must be provided to access this endpoint.', 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()->onceUsingId($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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Middleware\Api;
|
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class HandleStatelessRequest
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Ensure that the 'Set-Cookie' header is removed from the response if
|
|
||||||
* a bearer token is present and there is an api_key in the request attributes.
|
|
||||||
*
|
|
||||||
* This will also delete the session from the database automatically so that
|
|
||||||
* it is effectively treated as a stateless request. Any additional requests
|
|
||||||
* attempting to use that session will find no data.
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function handle(Request $request, Closure $next)
|
|
||||||
{
|
|
||||||
/** @var \Illuminate\Http\Response $response */
|
|
||||||
$response = $next($request);
|
|
||||||
|
|
||||||
if (!is_null($request->bearerToken()) && $request->isJson()) {
|
|
||||||
$request->session()->getHandler()->destroy(
|
|
||||||
$request->session()->getId()
|
|
||||||
);
|
|
||||||
|
|
||||||
$response->headers->remove('Set-Cookie');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Middleware;
|
namespace Pterodactyl\Http\Middleware;
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use Pterodactyl\Models\ApiKey;
|
|
||||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
|
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
|
||||||
|
|
||||||
class VerifyCsrfToken extends BaseVerifier
|
class VerifyCsrfToken extends BaseVerifier
|
||||||
|
@ -16,31 +14,4 @@ class VerifyCsrfToken extends BaseVerifier
|
||||||
* @var string[]
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
protected $except = ['remote/*', 'daemon/*'];
|
protected $except = ['remote/*', 'daemon/*'];
|
||||||
|
|
||||||
/**
|
|
||||||
* Manually apply CSRF protection to routes depending on the authentication
|
|
||||||
* mechanism being used. If the API request is using an API key that exists
|
|
||||||
* in the database we can safely ignore CSRF protections, since that would be
|
|
||||||
* a manually initiated request by a user or server.
|
|
||||||
*
|
|
||||||
* All other requests should go through the standard CSRF protections that
|
|
||||||
* Laravel affords us. This code will be removed in v2 since we have switched
|
|
||||||
* to using Sanctum for the API endpoints, which handles that for us automatically.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*
|
|
||||||
* @throws \Illuminate\Session\TokenMismatchException
|
|
||||||
*/
|
|
||||||
public function handle($request, Closure $next)
|
|
||||||
{
|
|
||||||
$key = $request->attributes->get('api_key');
|
|
||||||
|
|
||||||
if ($key instanceof ApiKey && $key->exists) {
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
return parent::handle($request, $next);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,19 +2,59 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Models;
|
namespace Pterodactyl\Models;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Pterodactyl\Services\Acl\Api\AdminAcl;
|
use Pterodactyl\Services\Acl\Api\AdminAcl;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Pterodactyl\Models\ApiKey.
|
||||||
|
*
|
||||||
* @property int $id
|
* @property int $id
|
||||||
* @property int $user_id
|
* @property int $user_id
|
||||||
* @property int $key_type
|
* @property int $key_type
|
||||||
* @property string $identifier
|
* @property string|null $identifier
|
||||||
* @property string $token
|
* @property string $token
|
||||||
* @property array $allowed_ips
|
* @property array|null $allowed_ips
|
||||||
* @property string $memo
|
* @property string|null $memo
|
||||||
* @property \Carbon\Carbon|null $last_used_at
|
* @property \Illuminate\Support\Carbon|null $last_used_at
|
||||||
* @property \Carbon\Carbon $created_at
|
* @property \Illuminate\Support\Carbon|null $created_at
|
||||||
* @property \Carbon\Carbon $updated_at
|
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||||
|
* @property int $r_servers
|
||||||
|
* @property int $r_nodes
|
||||||
|
* @property int $r_allocations
|
||||||
|
* @property int $r_users
|
||||||
|
* @property int $r_locations
|
||||||
|
* @property int $r_nests
|
||||||
|
* @property int $r_eggs
|
||||||
|
* @property int $r_database_hosts
|
||||||
|
* @property int $r_server_databases
|
||||||
|
* @property \Pterodactyl\Models\User $tokenable
|
||||||
|
* @property \Pterodactyl\Models\User $user
|
||||||
|
*
|
||||||
|
* @method static \Database\Factories\ApiKeyFactory factory(...$parameters)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey newModelQuery()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey newQuery()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey query()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereAllowedIps($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereCreatedAt($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereId($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereIdentifier($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereKeyType($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereLastUsedAt($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereMemo($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRAllocations($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRDatabaseHosts($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereREggs($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRLocations($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRNests($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRNodes($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRServerDatabases($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRServers($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRUsers($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereToken($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereUpdatedAt($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereUserId($value)
|
||||||
|
* @mixin \Eloquent
|
||||||
*/
|
*/
|
||||||
class ApiKey extends Model
|
class ApiKey extends Model
|
||||||
{
|
{
|
||||||
|
@ -23,21 +63,21 @@ class ApiKey extends Model
|
||||||
* API representation using fractal.
|
* API representation using fractal.
|
||||||
*/
|
*/
|
||||||
public const RESOURCE_NAME = 'api_key';
|
public const RESOURCE_NAME = 'api_key';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Different API keys that can exist on the system.
|
* Different API keys that can exist on the system.
|
||||||
*/
|
*/
|
||||||
public const TYPE_NONE = 0;
|
public const TYPE_NONE = 0;
|
||||||
public const TYPE_ACCOUNT = 1;
|
public const TYPE_ACCOUNT = 1;
|
||||||
|
/* @deprecated */
|
||||||
public const TYPE_APPLICATION = 2;
|
public const TYPE_APPLICATION = 2;
|
||||||
|
/* @deprecated */
|
||||||
public const TYPE_DAEMON_USER = 3;
|
public const TYPE_DAEMON_USER = 3;
|
||||||
|
/* @deprecated */
|
||||||
public const TYPE_DAEMON_APPLICATION = 4;
|
public const TYPE_DAEMON_APPLICATION = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The length of API key identifiers.
|
* The length of API key identifiers.
|
||||||
*/
|
*/
|
||||||
public const IDENTIFIER_LENGTH = 16;
|
public const IDENTIFIER_LENGTH = 16;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The length of the actual API key that is encrypted and stored
|
* The length of the actual API key that is encrypted and stored
|
||||||
* in the database.
|
* in the database.
|
||||||
|
@ -124,4 +164,47 @@ class ApiKey extends Model
|
||||||
self::UPDATED_AT,
|
self::UPDATED_AT,
|
||||||
'last_used_at',
|
'last_used_at',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the user this token is assigned to.
|
||||||
|
*/
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required for support with Laravel Sanctum.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
|
*
|
||||||
|
* @see \Laravel\Sanctum\Guard::supportsTokens()
|
||||||
|
*/
|
||||||
|
public function tokenable()
|
||||||
|
{
|
||||||
|
return $this->user();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the model matching the provided token.
|
||||||
|
*
|
||||||
|
* @param string $token
|
||||||
|
*
|
||||||
|
* @return self|null
|
||||||
|
*/
|
||||||
|
public static function findToken($token)
|
||||||
|
{
|
||||||
|
$id = Str::substr($token, 0, self::IDENTIFIER_LENGTH);
|
||||||
|
$token = Str::substr($token, strlen($id));
|
||||||
|
|
||||||
|
return static::where('identifier', $id)->where('token', encrypt($token))->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new identifier for an API key.
|
||||||
|
*/
|
||||||
|
public static function generateTokenIdentifier(): string
|
||||||
|
{
|
||||||
|
return 'ptdl_' . Str::random(self::IDENTIFIER_LENGTH - 5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
37
app/Models/Traits/HasAccessTokens.php
Normal file
37
app/Models/Traits/HasAccessTokens.php
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Models\Traits;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Laravel\Sanctum\Sanctum;
|
||||||
|
use Pterodactyl\Models\ApiKey;
|
||||||
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
use Pterodactyl\Extensions\Laravel\Sanctum\NewAccessToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @mixin \Pterodactyl\Models\Model
|
||||||
|
*/
|
||||||
|
trait HasAccessTokens
|
||||||
|
{
|
||||||
|
use HasApiTokens;
|
||||||
|
|
||||||
|
public function tokens()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Sanctum::$personalAccessTokenModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createToken(string $name, array $abilities = ['*'])
|
||||||
|
{
|
||||||
|
/** @var \Pterodactyl\Models\ApiKey $token */
|
||||||
|
$token = $this->tokens()->create([
|
||||||
|
'user_id' => $this->id,
|
||||||
|
'key_type' => ApiKey::TYPE_ACCOUNT,
|
||||||
|
'identifier' => ApiKey::generateTokenIdentifier(),
|
||||||
|
'token' => encrypt($plain = Str::random(ApiKey::KEY_LENGTH)),
|
||||||
|
'memo' => $name,
|
||||||
|
'allowed_ips' => [],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return new NewAccessToken($token, $token->identifier . $plain);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Pterodactyl\Models;
|
namespace Pterodactyl\Models;
|
||||||
|
|
||||||
use Pterodactyl\Rules\Username;
|
use Pterodactyl\Rules\Username;
|
||||||
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Validation\Rules\In;
|
use Illuminate\Validation\Rules\In;
|
||||||
use Illuminate\Auth\Authenticatable;
|
use Illuminate\Auth\Authenticatable;
|
||||||
|
@ -18,7 +19,7 @@ use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
|
||||||
use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification;
|
use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \Pterodactyl\Models\User.
|
* Pterodactyl\Models\User.
|
||||||
*
|
*
|
||||||
* @property int $id
|
* @property int $id
|
||||||
* @property string|null $external_id
|
* @property string|null $external_id
|
||||||
|
@ -28,27 +29,28 @@ use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification;
|
||||||
* @property string|null $name_first
|
* @property string|null $name_first
|
||||||
* @property string|null $name_last
|
* @property string|null $name_last
|
||||||
* @property string $password
|
* @property string $password
|
||||||
* @property string|null $remeber_token
|
* @property string|null $remember_token
|
||||||
* @property string $language
|
* @property string $language
|
||||||
* @property bool $root_admin
|
* @property bool $root_admin
|
||||||
* @property bool $use_totp
|
* @property bool $use_totp
|
||||||
* @property string|null $totp_secret
|
* @property string|null $totp_secret
|
||||||
* @property \Carbon\Carbon|null $totp_authenticated_at
|
* @property \Illuminate\Support\Carbon|null $totp_authenticated_at
|
||||||
* @property bool $gravatar
|
* @property bool $gravatar
|
||||||
* @property \Carbon\Carbon $created_at
|
* @property \Illuminate\Support\Carbon|null $created_at
|
||||||
* @property \Carbon\Carbon $updated_at
|
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||||
* @property string $name
|
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\ApiKey[] $apiKeys
|
||||||
* @property \Pterodactyl\Models\ApiKey[]|\Illuminate\Database\Eloquent\Collection $apiKeys
|
|
||||||
* @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers
|
|
||||||
* @property \Pterodactyl\Models\RecoveryToken[]|\Illuminate\Database\Eloquent\Collection $recoveryTokens
|
|
||||||
* @property string|null $remember_token
|
|
||||||
* @property int|null $api_keys_count
|
* @property int|null $api_keys_count
|
||||||
|
* @property string $name
|
||||||
* @property \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications
|
* @property \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications
|
||||||
* @property int|null $notifications_count
|
* @property int|null $notifications_count
|
||||||
|
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\RecoveryToken[] $recoveryTokens
|
||||||
* @property int|null $recovery_tokens_count
|
* @property int|null $recovery_tokens_count
|
||||||
|
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Server[] $servers
|
||||||
* @property int|null $servers_count
|
* @property int|null $servers_count
|
||||||
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\UserSSHKey[] $sshKeys
|
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\UserSSHKey[] $sshKeys
|
||||||
* @property int|null $ssh_keys_count
|
* @property int|null $ssh_keys_count
|
||||||
|
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\ApiKey[] $tokens
|
||||||
|
* @property int|null $tokens_count
|
||||||
*
|
*
|
||||||
* @method static \Database\Factories\UserFactory factory(...$parameters)
|
* @method static \Database\Factories\UserFactory factory(...$parameters)
|
||||||
* @method static Builder|User newModelQuery()
|
* @method static Builder|User newModelQuery()
|
||||||
|
@ -82,6 +84,7 @@ class User extends Model implements
|
||||||
use Authorizable;
|
use Authorizable;
|
||||||
use AvailableLanguages;
|
use AvailableLanguages;
|
||||||
use CanResetPassword;
|
use CanResetPassword;
|
||||||
|
use HasApiTokens;
|
||||||
use Notifiable;
|
use Notifiable;
|
||||||
|
|
||||||
public const USER_LEVEL_USER = 0;
|
public const USER_LEVEL_USER = 0;
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Providers;
|
namespace Pterodactyl\Providers;
|
||||||
|
|
||||||
|
use Laravel\Sanctum\Sanctum;
|
||||||
|
use Pterodactyl\Models\Server;
|
||||||
|
use Pterodactyl\Models\ApiKey;
|
||||||
|
use Pterodactyl\Policies\ServerPolicy;
|
||||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||||
|
|
||||||
class AuthServiceProvider extends ServiceProvider
|
class AuthServiceProvider extends ServiceProvider
|
||||||
|
@ -12,7 +16,7 @@ class AuthServiceProvider extends ServiceProvider
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $policies = [
|
protected $policies = [
|
||||||
'Pterodactyl\Models\Server' => 'Pterodactyl\Policies\ServerPolicy',
|
Server::class => ServerPolicy::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,6 +24,8 @@ class AuthServiceProvider extends ServiceProvider
|
||||||
*/
|
*/
|
||||||
public function boot()
|
public function boot()
|
||||||
{
|
{
|
||||||
|
Sanctum::usePersonalAccessTokenModel(ApiKey::class);
|
||||||
|
|
||||||
$this->registerPolicies();
|
$this->registerPolicies();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
"laracasts/utilities": "~3.2.1",
|
"laracasts/utilities": "~3.2.1",
|
||||||
"laravel/framework": "^8.83",
|
"laravel/framework": "^8.83",
|
||||||
"laravel/helpers": "~1.5.0",
|
"laravel/helpers": "~1.5.0",
|
||||||
|
"laravel/sanctum": "~2.15.1",
|
||||||
"laravel/tinker": "~2.7.2",
|
"laravel/tinker": "~2.7.2",
|
||||||
"laravel/ui": "~3.4.5",
|
"laravel/ui": "~3.4.5",
|
||||||
"lcobucci/jwt": "~4.1.5",
|
"lcobucci/jwt": "~4.1.5",
|
||||||
|
|
69
composer.lock
generated
69
composer.lock
generated
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "59024efe671be95afe14319b19606566",
|
"content-hash": "0368e946c40456bcd1fb007bfc3e7bf0",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "aws/aws-crt-php",
|
"name": "aws/aws-crt-php",
|
||||||
|
@ -1668,6 +1668,71 @@
|
||||||
},
|
},
|
||||||
"time": "2022-01-12T15:58:51+00:00"
|
"time": "2022-01-12T15:58:51+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel/sanctum",
|
||||||
|
"version": "v2.15.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/laravel/sanctum.git",
|
||||||
|
"reference": "31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/laravel/sanctum/zipball/31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473",
|
||||||
|
"reference": "31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"illuminate/console": "^6.9|^7.0|^8.0|^9.0",
|
||||||
|
"illuminate/contracts": "^6.9|^7.0|^8.0|^9.0",
|
||||||
|
"illuminate/database": "^6.9|^7.0|^8.0|^9.0",
|
||||||
|
"illuminate/support": "^6.9|^7.0|^8.0|^9.0",
|
||||||
|
"php": "^7.2|^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"mockery/mockery": "^1.0",
|
||||||
|
"orchestra/testbench": "^4.0|^5.0|^6.0|^7.0",
|
||||||
|
"phpunit/phpunit": "^8.0|^9.3"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.x-dev"
|
||||||
|
},
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Laravel\\Sanctum\\SanctumServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Laravel\\Sanctum\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Taylor Otwell",
|
||||||
|
"email": "taylor@laravel.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.",
|
||||||
|
"keywords": [
|
||||||
|
"auth",
|
||||||
|
"laravel",
|
||||||
|
"sanctum"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/laravel/sanctum/issues",
|
||||||
|
"source": "https://github.com/laravel/sanctum"
|
||||||
|
},
|
||||||
|
"time": "2022-04-08T13:39:49+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/serializable-closure",
|
"name": "laravel/serializable-closure",
|
||||||
"version": "v1.1.1",
|
"version": "v1.1.1",
|
||||||
|
@ -10773,5 +10838,5 @@
|
||||||
"platform-overrides": {
|
"platform-overrides": {
|
||||||
"php": "7.4.0"
|
"php": "7.4.0"
|
||||||
},
|
},
|
||||||
"plugin-api-version": "2.2.0"
|
"plugin-api-version": "2.3.0"
|
||||||
}
|
}
|
||||||
|
|
67
config/sanctum.php
Normal file
67
config/sanctum.php
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Laravel\Sanctum\Sanctum;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Stateful Domains
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Requests from the following domains / hosts will receive stateful API
|
||||||
|
| authentication cookies. Typically, these should include your local
|
||||||
|
| and production domains which access your API via a frontend SPA.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
|
||||||
|
'%s%s',
|
||||||
|
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
|
||||||
|
Sanctum::currentApplicationUrlWithPort()
|
||||||
|
))),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Sanctum Guards
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This array contains the authentication guards that will be checked when
|
||||||
|
| Sanctum is trying to authenticate a request. If none of these guards
|
||||||
|
| are able to authenticate the request, Sanctum will use the bearer
|
||||||
|
| token that's present on an incoming request for authentication.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'guard' => ['web'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Expiration Minutes
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This value controls the number of minutes until an issued token will be
|
||||||
|
| considered expired. If this value is null, personal access tokens do
|
||||||
|
| not expire. This won't tweak the lifetime of first-party sessions.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'expiration' => null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Sanctum Middleware
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When authenticating your first-party SPA with Sanctum you may need to
|
||||||
|
| customize some of the middleware Sanctum uses while processing the
|
||||||
|
| request. You may change the middleware listed below as required.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'middleware' => [
|
||||||
|
'verify_csrf_token' => Pterodactyl\Http\Middleware\VerifyCsrfToken::class,
|
||||||
|
'encrypt_cookies' => Pterodactyl\Http\Middleware\EncryptCookies::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
Loading…
Reference in a new issue