Very basic working implementation of sanctum for API validation

This commit is contained in:
Dane Everitt 2021-07-27 21:23:11 -07:00
parent 4b32828423
commit d60e8a193b
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
23 changed files with 24212 additions and 88 deletions

3
.gitignore vendored
View file

@ -77,3 +77,6 @@ yarn-error.log
!.env.example !.env.example
.env* .env*
*.log *.log
_ide_helper_models.php
_ide_helper.php
.phpstorm.meta.php

2267
.phpstorm.meta.php Normal file

File diff suppressed because it is too large Load diff

20589
_ide_helper.php Normal file

File diff suppressed because it is too large Load diff

1128
_ide_helper_models.php Normal file

File diff suppressed because it is too large Load diff

View file

@ -12,11 +12,14 @@ use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
use Pterodactyl\Transformers\Api\Client\ApiKeyTransformer; use Pterodactyl\Transformers\Api\Client\ApiKeyTransformer;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Pterodactyl\Http\Requests\Api\Client\Account\StoreApiKeyRequest; use Pterodactyl\Http\Requests\Api\Client\Account\StoreApiKeyRequest;
use Pterodactyl\Transformers\Api\Client\PersonalAccessTokenTransformer;
class ApiKeyController extends ClientApiController class ApiKeyController extends ClientApiController
{ {
private Encrypter $encrypter; private Encrypter $encrypter;
private ApiKeyRepository $repository; private ApiKeyRepository $repository;
private KeyCreationService $keyCreationService; private KeyCreationService $keyCreationService;
/** /**
@ -41,8 +44,8 @@ class ApiKeyController extends ClientApiController
*/ */
public function index(ClientApiRequest $request): array public function index(ClientApiRequest $request): array
{ {
return $this->fractal->collection($request->user()->apiKeys) return $this->fractal->collection($request->user()->tokens)
->transformWith($this->getTransformer(ApiKeyTransformer::class)) ->transformWith($this->getTransformer(PersonalAccessTokenTransformer::class))
->toArray(); ->toArray();
} }
@ -50,25 +53,22 @@ class ApiKeyController extends ClientApiController
* Store a new API key for a user's account. * Store a new API key for a user's account.
* *
* @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Illuminate\Contracts\Container\BindingResolutionException * @throws \Illuminate\Contracts\Container\BindingResolutionException
*/ */
public function store(StoreApiKeyRequest $request): array public function store(StoreApiKeyRequest $request): array
{ {
if ($request->user()->apiKeys->count() >= 5) { if ($request->user()->tokens->count() >= 10) {
throw new DisplayException('You have reached the account limit for number of API keys.'); throw new DisplayException('You have reached the account limit for number of API keys.');
} }
$key = $this->keyCreationService->setKeyType(ApiKey::TYPE_ACCOUNT)->handle([ // TODO: this should accept an array of different scopes to apply as permissions
'user_id' => $request->user()->id, // for the token. Right now it allows any account level permission.
'memo' => $request->input('description'), $token = $request->user()->createToken($request->input('description'));
'allowed_ips' => $request->input('allowed_ips') ?? [],
]);
return $this->fractal->item($key) return $this->fractal->item($token->accessToken)
->transformWith($this->getTransformer(ApiKeyTransformer::class)) ->transformWith($this->getTransformer(PersonalAccessTokenTransformer::class))
->addMeta([ ->addMeta([
'secret_token' => $this->encrypter->decrypt($key->token), 'secret_token' => $token->plainTextToken,
]) ])
->toArray(); ->toArray();
} }
@ -78,15 +78,7 @@ class ApiKeyController extends ClientApiController
*/ */
public function delete(ClientApiRequest $request, string $identifier): Response public function delete(ClientApiRequest $request, string $identifier): Response
{ {
$response = $this->repository->deleteWhere([ $request->user()->tokens()->where('id', $identifier)->delete();
'key_type' => ApiKey::TYPE_ACCOUNT,
'user_id' => $request->user()->id,
'identifier' => $identifier,
]);
if (!$response) {
throw new NotFoundHttpException();
}
return $this->returnNoContent(); return $this->returnNoContent();
} }

View file

@ -59,7 +59,7 @@ abstract class ClientApiController extends ApplicationApiController
]); ]);
if ($transformer instanceof BaseClientTransformer) { if ($transformer instanceof BaseClientTransformer) {
$transformer->setKey($this->request->attributes->get('api_key')); // $transformer->setKey($this->request->attributes->get('api_key'));
$transformer->setUser($this->request->user()); $transformer->setUser($this->request->user());
} }

View file

@ -123,8 +123,6 @@ class LoginController extends AbstractLoginController
]); ]);
} }
$this->auth->guard()->login($user, true);
return $this->sendLoginResponse($user, $request); return $this->sendLoginResponse($user, $request);
} }
} }

View file

@ -32,7 +32,9 @@ 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 Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientApiBindings; use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientApiBindings;
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance;
use Pterodactyl\Http\Middleware\Api\Application\AuthenticateApplicationUser; use Pterodactyl\Http\Middleware\Api\Application\AuthenticateApplicationUser;
class Kernel extends HttpKernel class Kernel extends HttpKernel
@ -43,12 +45,11 @@ class Kernel extends HttpKernel
* @var array * @var array
*/ */
protected $middleware = [ protected $middleware = [
CheckForMaintenanceMode::class, TrustProxies::class,
EncryptCookies::class, PreventRequestsDuringMaintenance::class,
ValidatePostSize::class, ValidatePostSize::class,
TrimStrings::class, TrimStrings::class,
ConvertEmptyStringsToNull::class, ConvertEmptyStringsToNull::class,
TrustProxies::class,
]; ];
/** /**
@ -58,6 +59,7 @@ class Kernel extends HttpKernel
*/ */
protected $middlewareGroups = [ protected $middlewareGroups = [
'web' => [ 'web' => [
EncryptCookies::class,
AddQueuedCookiesToResponse::class, AddQueuedCookiesToResponse::class,
StartSession::class, StartSession::class,
AuthenticateSession::class, AuthenticateSession::class,
@ -70,19 +72,23 @@ class Kernel extends HttpKernel
'api' => [ 'api' => [
IsValidJson::class, IsValidJson::class,
ApiSubstituteBindings::class, ApiSubstituteBindings::class,
SetSessionDriver::class, EnsureFrontendRequestsAreStateful::class,
'api..key:' . ApiKey::TYPE_APPLICATION, // SetSessionDriver::class,
// 'api..key:' . ApiKey::TYPE_APPLICATION,
AuthenticateApplicationUser::class, AuthenticateApplicationUser::class,
AuthenticateIPAccess::class, // AuthenticateIPAccess::class,
], ],
'client-api' => [ 'client-api' => [
StartSession::class, // StartSession::class,
SetSessionDriver::class, // SetSessionDriver::class,
AuthenticateSession::class, // AuthenticateSession::class,
IsValidJson::class, IsValidJson::class,
EnsureFrontendRequestsAreStateful::class,
'auth:sanctum',
// 'throttle:api',
SubstituteClientApiBindings::class, SubstituteClientApiBindings::class,
'api..key:' . ApiKey::TYPE_ACCOUNT, // 'api..key:' . ApiKey::TYPE_ACCOUNT,
AuthenticateIPAccess::class, // AuthenticateIPAccess::class,
// This is perhaps a little backwards with the Client API, but logically you'd be unable // 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 // to create/get an API key without first enabling 2FA on the account, so I suppose in the
// end it makes sense. // end it makes sense.

View file

@ -14,6 +14,6 @@ class VerifyCsrfToken extends BaseVerifier
protected $except = [ protected $except = [
'remote/*', 'remote/*',
'daemon/*', 'daemon/*',
'api/*', // 'api/*',
]; ];
} }

View file

@ -4,18 +4,6 @@ namespace Pterodactyl\Models;
use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Services\Acl\Api\AdminAcl;
/**
* @property int $id
* @property int $user_id
* @property int $key_type
* @property string $identifier
* @property string $token
* @property array $allowed_ips
* @property string $memo
* @property \Carbon\Carbon|null $last_used_at
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
*/
class ApiKey extends Model class ApiKey extends Model
{ {
/** /**

View file

@ -0,0 +1,10 @@
<?php
namespace Pterodactyl\Models;
use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;
class PersonalAccessToken extends SanctumPersonalAccessToken
{
public const RESOURCE_NAME = 'personal_access_token';
}

View file

@ -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\Auth\Authenticatable; use Illuminate\Auth\Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
@ -12,36 +13,12 @@ use Pterodactyl\Traits\Helpers\AvailableLanguages;
use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Foundation\Auth\Access\Authorizable; use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification;
/**
* @property int $id
* @property string|null $external_id
* @property string $uuid
* @property string $username
* @property string $email
* @property string $password
* @property string|null $remember_token
* @property string $language
* @property int $admin_role_id
* @property bool $root_admin
* @property bool $use_totp
* @property string|null $totp_secret
* @property \Carbon\Carbon|null $totp_authenticated_at
* @property bool $gravatar
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property string $name
* @property \Pterodactyl\Models\AdminRole $adminRole
* @property \Pterodactyl\Models\ApiKey[]|\Illuminate\Database\Eloquent\Collection $apiKeys
* @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers
* @property \Pterodactyl\Models\UserSSHKey|\Illuminate\Database\Eloquent\Collection $sshKeys
* @property \Pterodactyl\Models\RecoveryToken[]|\Illuminate\Database\Eloquent\Collection $recoveryTokens
* @property \Pterodactyl\Models\WebauthnKey[]|\Illuminate\Database\Eloquent\Collection $webauthnKeys
*/
class User extends Model implements class User extends Model implements
AuthenticatableContract, AuthenticatableContract,
AuthorizableContract, AuthorizableContract,
@ -51,6 +28,8 @@ class User extends Model implements
use Authorizable; use Authorizable;
use AvailableLanguages; use AvailableLanguages;
use CanResetPassword; use CanResetPassword;
use HasApiTokens;
use HasFactory;
use Notifiable; use Notifiable;
public const USER_LEVEL_USER = 0; public const USER_LEVEL_USER = 0;

View file

@ -3,6 +3,7 @@
namespace Pterodactyl\Providers; namespace Pterodactyl\Providers;
use Pterodactyl\Models\User; use Pterodactyl\Models\User;
use Laravel\Sanctum\Sanctum;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
use Pterodactyl\Models\Subuser; use Pterodactyl\Models\Subuser;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
@ -10,6 +11,7 @@ use Illuminate\Support\ServiceProvider;
use Pterodactyl\Observers\UserObserver; use Pterodactyl\Observers\UserObserver;
use Pterodactyl\Observers\ServerObserver; use Pterodactyl\Observers\ServerObserver;
use Pterodactyl\Observers\SubuserObserver; use Pterodactyl\Observers\SubuserObserver;
use Pterodactyl\Models\PersonalAccessToken;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
@ -23,6 +25,11 @@ class AppServiceProvider extends ServiceProvider
User::observe(UserObserver::class); User::observe(UserObserver::class);
Server::observe(ServerObserver::class); Server::observe(ServerObserver::class);
Subuser::observe(SubuserObserver::class); Subuser::observe(SubuserObserver::class);
/**
* @see https://laravel.com/docs/8.x/sanctum#overriding-default-models
*/
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
} }
/** /**

View file

@ -48,7 +48,7 @@ class RouteServiceProvider extends ServiceProvider
->group(base_path('routes/api-application.php')); ->group(base_path('routes/api-application.php'));
Route::middleware([ Route::middleware([
sprintf('throttle:%s,%s', config('http.rate_limit.client'), config('http.rate_limit.client_period')), //sprintf('throttle:%s,%s', config('http.rate_limit.client'), config('http.rate_limit.client_period')),
'client-api', 'client-api',
])->prefix('/api/client') ])->prefix('/api/client')
->namespace($this->namespace . '\Api\Client') ->namespace($this->namespace . '\Api\Client')

View file

@ -84,11 +84,12 @@ abstract class BaseTransformer extends TransformerAbstract
*/ */
protected function authorize(string $resource): bool protected function authorize(string $resource): bool
{ {
if ($this->getKey()->key_type === ApiKey::TYPE_ACCOUNT && $this->isRootAdmin()) { return true;
return true; // if ($this->getKey()->key_type === ApiKey::TYPE_ACCOUNT && $this->isRootAdmin()) {
} // return true;
// }
return AdminAcl::check($this->getKey(), $resource, AdminAcl::READ); //
// return AdminAcl::check($this->getKey(), $resource, AdminAcl::READ);
} }
/** /**
@ -104,7 +105,7 @@ abstract class BaseTransformer extends TransformerAbstract
{ {
/** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */ /** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */
$transformer = Container::getInstance()->makeWith($abstract, $parameters); $transformer = Container::getInstance()->makeWith($abstract, $parameters);
$transformer->setKey($this->getKey()); // $transformer->setKey($this->getKey());
if (!$transformer instanceof self) { if (!$transformer instanceof self) {
throw new InvalidTransformerLevelException('Calls to ' . __METHOD__ . ' must return a transformer that is an instance of ' . __CLASS__); throw new InvalidTransformerLevelException('Calls to ' . __METHOD__ . ' must return a transformer that is an instance of ' . __CLASS__);

View file

@ -58,7 +58,7 @@ abstract class BaseClientTransformer extends BaseApplicationTransformer
{ {
/** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */ /** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */
$transformer = Container::getInstance()->makeWith($abstract, $parameters); $transformer = Container::getInstance()->makeWith($abstract, $parameters);
$transformer->setKey($this->getKey()); // $transformer->setKey($this->getKey());
if (!$transformer instanceof self) { if (!$transformer instanceof self) {
throw new InvalidTransformerLevelException('Calls to ' . __METHOD__ . ' must return a transformer that is an instance of ' . __CLASS__); throw new InvalidTransformerLevelException('Calls to ' . __METHOD__ . ' must return a transformer that is an instance of ' . __CLASS__);

View file

@ -0,0 +1,32 @@
<?php
namespace Pterodactyl\Transformers\Api\Client;
use Pterodactyl\Models\PersonalAccessToken;
class PersonalAccessTokenTransformer extends BaseClientTransformer
{
/**
* @return string
*/
public function getResourceName(): string
{
return PersonalAccessToken::RESOURCE_NAME;
}
/**
* @param \Pterodactyl\Models\PersonalAccessToken $model
* @return array
*/
public function transform(PersonalAccessToken $model): array
{
return [
'id' => $model->tokenable_id,
'name' => $model->name,
'abilities' => $model->abilities ?? [],
'last_used_at' => $model->last_used_at ? $model->last_used_at->toIso8601String() : null,
'created_at' => $model->created_at->toIso8601String(),
'updated_at' => $model->updated_at->toIso8601String(),
];
}
}

View file

@ -38,7 +38,8 @@ class ServerTransformer extends BaseClientTransformer
$service = Container::getInstance()->make(StartupCommandService::class); $service = Container::getInstance()->make(StartupCommandService::class);
return [ return [
'server_owner' => $this->getKey()->user_id === $server->owner_id, 'server_owner' => true,
// 'server_owner' => $this->getKey()->user_id === $server->owner_id,
'identifier' => $server->uuidShort, 'identifier' => $server->uuidShort,
'internal_id' => $server->id, 'internal_id' => $server->id,
'uuid' => $server->uuid, 'uuid' => $server->uuid,

View file

@ -26,6 +26,7 @@
"laracasts/utilities": "^3.2", "laracasts/utilities": "^3.2",
"laravel/framework": "^8.52", "laravel/framework": "^8.52",
"laravel/helpers": "^1.4", "laravel/helpers": "^1.4",
"laravel/sanctum": "^2.11",
"laravel/tinker": "^2.6", "laravel/tinker": "^2.6",
"laravel/ui": "^3.3", "laravel/ui": "^3.3",
"lcobucci/jwt": "^4.1", "lcobucci/jwt": "^4.1",

33
config/cors.php Normal file
View file

@ -0,0 +1,33 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
'paths' => ['api/*'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 7200,
'supports_credentials' => true,
];

51
config/sanctum.php Normal file
View file

@ -0,0 +1,51 @@
<?php
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,pterodactyl.test',
env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : ''
))),
/*
|--------------------------------------------------------------------------
| 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,
],
];

View file

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePersonalAccessTokensTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('personal_access_tokens', function (Blueprint $table) {
$table->bigIncrements('id');
$table->morphs('tokenable');
$table->string('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('personal_access_tokens');
}
}

View file

@ -1,12 +1,11 @@
<?php <?php
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Pterodactyl\Http\Controllers\Api\Client;
use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication;
use Pterodactyl\Http\Middleware\Api\Client\Server\ResourceBelongsToServer; use Pterodactyl\Http\Middleware\Api\Client\Server\ResourceBelongsToServer;
use Pterodactyl\Http\Middleware\Api\Client\Server\AuthenticateServerAccess; use Pterodactyl\Http\Middleware\Api\Client\Server\AuthenticateServerAccess;
use Pterodactyl\Http\Controllers\Api\Client;
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Client Control API | Client Control API
@ -27,9 +26,9 @@ Route::group(['prefix' => '/account'], function () {
Route::put('/email', 'AccountController@updateEmail')->name('api:client.account.update-email'); Route::put('/email', 'AccountController@updateEmail')->name('api:client.account.update-email');
Route::put('/password', 'AccountController@updatePassword')->name('api:client.account.update-password'); Route::put('/password', 'AccountController@updatePassword')->name('api:client.account.update-password');
Route::get('/api-keys', 'ApiKeyController@index'); Route::get('/api-keys', [Client\ApiKeyController::class, 'index']);
Route::post('/api-keys', 'ApiKeyController@store'); Route::post('/api-keys', [Client\ApiKeyController::class, 'store']);
Route::delete('/api-keys/{identifier}', 'ApiKeyController@delete'); Route::delete('/api-keys/{identifier}', [Client\ApiKeyController::class, 'delete']);
Route::get('/webauthn', 'WebauthnController@index')->withoutMiddleware(RequireTwoFactorAuthentication::class); Route::get('/webauthn', 'WebauthnController@index')->withoutMiddleware(RequireTwoFactorAuthentication::class);
Route::get('/webauthn/register', 'WebauthnController@register')->withoutMiddleware(RequireTwoFactorAuthentication::class); Route::get('/webauthn/register', 'WebauthnController@register')->withoutMiddleware(RequireTwoFactorAuthentication::class);
@ -49,7 +48,10 @@ Route::group(['prefix' => '/account'], function () {
| Endpoint: /api/client/servers/{server} | Endpoint: /api/client/servers/{server}
| |
*/ */
Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServerAccess::class, ResourceBelongsToServer::class]], function () { Route::group([
'prefix' => '/servers/{server}',
'middleware' => [AuthenticateServerAccess::class, ResourceBelongsToServer::class],
], function () {
Route::get('/', 'Servers\ServerController@index')->name('api:client:server.view'); Route::get('/', 'Servers\ServerController@index')->name('api:client:server.view');
Route::get('/websocket', 'Servers\WebsocketController')->name('api:client:server.ws'); Route::get('/websocket', 'Servers\WebsocketController')->name('api:client:server.ws');
Route::get('/resources', 'Servers\ResourceUtilizationController')->name('api:client:server.resources'); Route::get('/resources', 'Servers\ResourceUtilizationController')->name('api:client:server.resources');