diff --git a/app/Console/Commands/Migration/CleanOrphanedApiKeysCommand.php b/app/Console/Commands/Migration/CleanOrphanedApiKeysCommand.php new file mode 100644 index 000000000..b9e007ee7 --- /dev/null +++ b/app/Console/Commands/Migration/CleanOrphanedApiKeysCommand.php @@ -0,0 +1,58 @@ +repository = $repository; + } + + /** + * Delete all orphaned API keys from the database when upgrading from 0.6 to 0.7. + * + * @return null|void + */ + public function handle() + { + $count = $this->repository->findCountWhere([['key_type', '=', ApiKey::TYPE_NONE]]); + $continue = $this->confirm( + 'This action will remove ' . $count . ' keys from the database. Are you sure you wish to continue?', false + ); + + if (! $continue) { + return null; + } + + $this->info('Deleting keys...'); + $this->repository->deleteWhere([['key_type', '=', ApiKey::TYPE_NONE]]); + $this->info('Keys were successfully deleted.'); + } +} diff --git a/app/Contracts/Repository/ApiKeyRepositoryInterface.php b/app/Contracts/Repository/ApiKeyRepositoryInterface.php index 18a53de24..54a4edd59 100644 --- a/app/Contracts/Repository/ApiKeyRepositoryInterface.php +++ b/app/Contracts/Repository/ApiKeyRepositoryInterface.php @@ -1,25 +1,26 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Contracts\Repository; -use Pterodactyl\Models\ApiKey; +use Pterodactyl\Models\User; +use Illuminate\Support\Collection; interface ApiKeyRepositoryInterface extends RepositoryInterface { /** - * Load permissions for a key onto the model. + * Get all of the account API keys that exist for a specific user. * - * @param \Pterodactyl\Models\ApiKey $model - * @param bool $refresh - * @deprecated - * @return \Pterodactyl\Models\ApiKey + * @param \Pterodactyl\Models\User $user + * @return \Illuminate\Support\Collection */ - public function loadPermissions(ApiKey $model, bool $refresh = false): ApiKey; + public function getAccountKeys(User $user): Collection; + + /** + * Delete an account API key from the panel for a specific user. + * + * @param \Pterodactyl\Models\User $user + * @param string $identifier + * @return int + */ + public function deleteAccountKey(User $user, string $identifier): int; } diff --git a/app/Http/Controllers/Base/APIController.php b/app/Http/Controllers/Base/AccountKeyController.php similarity index 66% rename from app/Http/Controllers/Base/APIController.php rename to app/Http/Controllers/Base/AccountKeyController.php index d2e74a66e..04563ca8a 100644 --- a/app/Http/Controllers/Base/APIController.php +++ b/app/Http/Controllers/Base/AccountKeyController.php @@ -2,14 +2,17 @@ namespace Pterodactyl\Http\Controllers\Base; +use Illuminate\View\View; use Illuminate\Http\Request; +use Illuminate\Http\Response; +use Pterodactyl\Models\ApiKey; use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Api\KeyCreationService; -use Pterodactyl\Http\Requests\Base\ApiKeyFormRequest; +use Pterodactyl\Http\Requests\Base\StoreAccountKeyRequest; use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface; -class APIController extends Controller +class AccountKeyController extends Controller { /** * @var \Prologue\Alerts\AlertsMessageBag @@ -44,49 +47,44 @@ class APIController extends Controller } /** - * Display base API index page. + * Display a listing of all account API keys. * * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function index(Request $request) + public function index(Request $request): View { return view('base.api.index', [ - 'keys' => $this->repository->findWhere([['user_id', '=', $request->user()->id]]), + 'keys' => $this->repository->getAccountKeys($request->user()), ]); } /** - * Display API key creation page. + * Display account API key creation page. * * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function create(Request $request) + public function create(Request $request): View { + return view('base.api.new'); } /** - * Handle saving new API key. + * Handle saving new account API key. * - * @param \Pterodactyl\Http\Requests\Base\ApiKeyFormRequest $request + * @param \Pterodactyl\Http\Requests\Base\StoreAccountKeyRequest $request * @return \Illuminate\Http\RedirectResponse * - * @throws \Exception * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function store(ApiKeyFormRequest $request) + public function store(StoreAccountKeyRequest $request) { - $adminPermissions = []; - if ($request->user()->root_admin) { - $adminPermissions = $request->input('admin_permissions', []); - } - - $secret = $this->keyService->handle([ + $this->keyService->setKeyType(ApiKey::TYPE_ACCOUNT)->handle([ 'user_id' => $request->user()->id, 'allowed_ips' => $request->input('allowed_ips'), 'memo' => $request->input('memo'), - ], $request->input('permissions', []), $adminPermissions); + ]); $this->alert->success(trans('base.api.index.keypair_created'))->flash(); @@ -94,18 +92,15 @@ class APIController extends Controller } /** - * @param \Illuminate\Http\Request $request - * @param string $key - * @return \Illuminate\Http\Response + * Delete an account API key from the Panel via an AJAX request. * - * @throws \Exception + * @param \Illuminate\Http\Request $request + * @param string $identifier + * @return \Illuminate\Http\Response */ - public function revoke(Request $request, $key) + public function revoke(Request $request, string $identifier): Response { - $this->repository->deleteWhere([ - ['user_id', '=', $request->user()->id], - ['token', '=', $key], - ]); + $this->repository->deleteAccountKey($request->user(), $identifier); return response('', 204); } diff --git a/app/Http/Middleware/Api/Admin/AuthenticateKey.php b/app/Http/Middleware/Api/Admin/AuthenticateKey.php index 7b9fb2f1d..864d4e3a7 100644 --- a/app/Http/Middleware/Api/Admin/AuthenticateKey.php +++ b/app/Http/Middleware/Api/Admin/AuthenticateKey.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Http\Middleware\Api\Admin; use Closure; +use Cake\Chronos\Chronos; use Illuminate\Http\Request; use Pterodactyl\Models\ApiKey; use Illuminate\Auth\AuthManager; @@ -51,8 +52,8 @@ class AuthenticateKey * @param \Closure $next * @return mixed * - * @throws \Symfony\Component\HttpKernel\Exception\HttpException - * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function handle(Request $request, Closure $next) { @@ -65,7 +66,10 @@ class AuthenticateKey $token = substr($raw, ApiKey::IDENTIFIER_LENGTH); try { - $model = $this->repository->findFirstWhere([['identifier', '=', $identifier]]); + $model = $this->repository->findFirstWhere([ + ['identifier', '=', $identifier], + ['key_type', '=', ApiKey::TYPE_APPLICATION], + ]); } catch (RecordNotFoundException $exception) { throw new AccessDeniedHttpException; } @@ -76,6 +80,7 @@ class AuthenticateKey $this->auth->guard()->loginUsingId($model->user_id); $request->attributes->set('api_key', $model); + $this->repository->withoutFreshModel()->update($model->id, ['last_used_at' => Chronos::now()]); return $next($request); } diff --git a/app/Http/Requests/Base/StoreAccountKeyRequest.php b/app/Http/Requests/Base/StoreAccountKeyRequest.php new file mode 100644 index 000000000..ab3906636 --- /dev/null +++ b/app/Http/Requests/Base/StoreAccountKeyRequest.php @@ -0,0 +1,23 @@ + 'required|nullable|string|max:500', + 'allowed_ips' => 'present', + 'allowed_ips.*' => 'sometimes|string', + ]; + } +} diff --git a/app/Models/ApiKey.php b/app/Models/ApiKey.php index ed53a3181..8379134ca 100644 --- a/app/Models/ApiKey.php +++ b/app/Models/ApiKey.php @@ -6,7 +6,6 @@ use Sofa\Eloquence\Eloquence; use Sofa\Eloquence\Validable; use Illuminate\Database\Eloquent\Model; use Pterodactyl\Services\Acl\Api\AdminAcl; -use Illuminate\Contracts\Encryption\Encrypter; use Sofa\Eloquence\Contracts\CleansAttributes; use Sofa\Eloquence\Contracts\Validable as ValidableContract; @@ -18,7 +17,7 @@ class ApiKey extends Model implements CleansAttributes, ValidableContract * Different API keys that can exist on the system. */ const TYPE_NONE = 0; - const TYPE_USER = 1; + const TYPE_ACCOUNT = 1; const TYPE_APPLICATION = 2; const TYPE_DAEMON_USER = 3; const TYPE_DAEMON_APPLICATION = 4; @@ -70,6 +69,7 @@ class ApiKey extends Model implements CleansAttributes, ValidableContract 'token', 'allowed_ips', 'memo', + 'last_used_at', ]; /** @@ -90,6 +90,7 @@ class ApiKey extends Model implements CleansAttributes, ValidableContract 'memo' => 'required', 'user_id' => 'required', 'token' => 'required', + 'key_type' => 'present', ]; /** @@ -99,6 +100,7 @@ class ApiKey extends Model implements CleansAttributes, ValidableContract */ protected static $dataIntegrityRules = [ 'user_id' => 'exists:users,id', + 'key_type' => 'integer|min:0|max:4', 'identifier' => 'string|size:16|unique:api_keys,identifier', 'token' => 'string', 'memo' => 'nullable|string|max:500', @@ -123,14 +125,4 @@ class ApiKey extends Model implements CleansAttributes, ValidableContract self::UPDATED_AT, 'last_used_at', ]; - - /** - * Return a decrypted version of the token. - * - * @return string - */ - public function getDecryptedTokenAttribute() - { - return app()->make(Encrypter::class)->decrypt($this->token); - } } diff --git a/app/Repositories/Eloquent/ApiKeyRepository.php b/app/Repositories/Eloquent/ApiKeyRepository.php index c7b9bd0a4..091122d89 100644 --- a/app/Repositories/Eloquent/ApiKeyRepository.php +++ b/app/Repositories/Eloquent/ApiKeyRepository.php @@ -2,7 +2,9 @@ namespace Pterodactyl\Repositories\Eloquent; +use Pterodactyl\Models\User; use Pterodactyl\Models\ApiKey; +use Illuminate\Support\Collection; use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface; class ApiKeyRepository extends EloquentRepository implements ApiKeyRepositoryInterface @@ -18,19 +20,30 @@ class ApiKeyRepository extends EloquentRepository implements ApiKeyRepositoryInt } /** - * Load permissions for a key onto the model. + * Get all of the account API keys that exist for a specific user. * - * @param \Pterodactyl\Models\ApiKey $model - * @param bool $refresh - * @deprecated - * @return \Pterodactyl\Models\ApiKey + * @param \Pterodactyl\Models\User $user + * @return \Illuminate\Support\Collection */ - public function loadPermissions(ApiKey $model, bool $refresh = false): ApiKey + public function getAccountKeys(User $user): Collection { - if (! $model->relationLoaded('permissions') || $refresh) { - $model->load('permissions'); - } + return $this->getBuilder()->where('user_id', $user->id) + ->where('key_type', ApiKey::TYPE_ACCOUNT) + ->get($this->getColumns()); + } - return $model; + /** + * Delete an account API key from the panel for a specific user. + * + * @param \Pterodactyl\Models\User $user + * @param string $identifier + * @return int + */ + public function deleteAccountKey(User $user, string $identifier): int + { + return $this->getBuilder()->where('user_id', $user->id) + ->where('key_type', ApiKey::TYPE_ACCOUNT) + ->where('identifier', $identifier) + ->delete(); } } diff --git a/app/Services/Api/KeyCreationService.php b/app/Services/Api/KeyCreationService.php index d8f308336..4a8efe7b4 100644 --- a/app/Services/Api/KeyCreationService.php +++ b/app/Services/Api/KeyCreationService.php @@ -13,6 +13,11 @@ class KeyCreationService */ private $encrypter; + /** + * @var int + */ + private $keyType = ApiKey::TYPE_NONE; + /** * @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface */ @@ -30,23 +35,43 @@ class KeyCreationService $this->repository = $repository; } + /** + * Set the type of key that should be created. By default an orphaned key will be + * created. These keys cannot be used for anything, and will not render in the UI. + * + * @param int $type + * @return \Pterodactyl\Services\Api\KeyCreationService + */ + public function setKeyType(int $type) + { + $this->keyType = $type; + + return $this; + } + /** * Create a new API key for the Panel using the permissions passed in the data request. * This will automatically generate an identifer and an encrypted token that are * stored in the database. * * @param array $data + * @param array $permissions * @return \Pterodactyl\Models\ApiKey * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function handle(array $data): ApiKey + public function handle(array $data, array $permissions = []): ApiKey { $data = array_merge($data, [ + 'key_type' => $this->keyType, 'identifier' => str_random(ApiKey::IDENTIFIER_LENGTH), 'token' => $this->encrypter->encrypt(str_random(ApiKey::KEY_LENGTH)), ]); + if ($this->keyType === ApiKey::TYPE_APPLICATION) { + $data = array_merge($data, $permissions); + } + $instance = $this->repository->create($data, true, true); return $instance; diff --git a/public/js/laroute.js b/public/js/laroute.js index ea2d1a03d..57cf645bd 100644 --- a/public/js/laroute.js +++ b/public/js/laroute.js @@ -5,8 +5,8 @@ var routes = { absolute: false, - rootUrl: 'http://pterodactyl.app', - routes : [{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/open","name":"debugbar.openhandler","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@handle"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/clockwork\/{id}","name":"debugbar.clockwork","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@clockwork"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/stylesheets","name":"debugbar.assets.css","action":"Barryvdh\Debugbar\Controllers\AssetController@css"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/javascript","name":"debugbar.assets.js","action":"Barryvdh\Debugbar\Controllers\AssetController@js"},{"host":null,"methods":["GET","HEAD"],"uri":"\/","name":"index","action":"Pterodactyl\Http\Controllers\Base\IndexController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"status\/{server}","name":"index.status","action":"Pterodactyl\Http\Controllers\Base\IndexController@status"},{"host":null,"methods":["GET","HEAD"],"uri":"account","name":"account","action":"Pterodactyl\Http\Controllers\Base\AccountController@index"},{"host":null,"methods":["POST"],"uri":"account","name":null,"action":"Pterodactyl\Http\Controllers\Base\AccountController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api","name":"account.api","action":"Pterodactyl\Http\Controllers\Base\APIController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api\/new","name":"account.api.new","action":"Pterodactyl\Http\Controllers\Base\APIController@create"},{"host":null,"methods":["POST"],"uri":"account\/api\/new","name":null,"action":"Pterodactyl\Http\Controllers\Base\APIController@store"},{"host":null,"methods":["DELETE"],"uri":"account\/api\/revoke\/{key}","name":"account.api.revoke","action":"Pterodactyl\Http\Controllers\Base\APIController@revoke"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security","name":"account.security","action":"Pterodactyl\Http\Controllers\Base\SecurityController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security\/revoke\/{id}","name":"account.security.revoke","action":"Pterodactyl\Http\Controllers\Base\SecurityController@revoke"},{"host":null,"methods":["PUT"],"uri":"account\/security\/totp","name":"account.security.totp","action":"Pterodactyl\Http\Controllers\Base\SecurityController@generateTotp"},{"host":null,"methods":["POST"],"uri":"account\/security\/totp","name":"account.security.totp.set","action":"Pterodactyl\Http\Controllers\Base\SecurityController@setTotp"},{"host":null,"methods":["DELETE"],"uri":"account\/security\/totp","name":"account.security.totp.disable","action":"Pterodactyl\Http\Controllers\Base\SecurityController@disableTotp"},{"host":null,"methods":["GET","HEAD"],"uri":"admin","name":"admin.index","action":"Pterodactyl\Http\Controllers\Admin\BaseController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations","name":"admin.locations","action":"Pterodactyl\Http\Controllers\Admin\LocationController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations\/view\/{location}","name":"admin.locations.view","action":"Pterodactyl\Http\Controllers\Admin\LocationController@view"},{"host":null,"methods":["POST"],"uri":"admin\/locations","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@create"},{"host":null,"methods":["PATCH"],"uri":"admin\/locations\/view\/{location}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases","name":"admin.databases","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases\/view\/{host}","name":"admin.databases.view","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@view"},{"host":null,"methods":["POST"],"uri":"admin\/databases","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@create"},{"host":null,"methods":["PATCH"],"uri":"admin\/databases\/view\/{host}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/databases\/view\/{host}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings","name":"admin.settings","action":"Pterodactyl\Http\Controllers\Admin\Settings\IndexController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings\/mail","name":"admin.settings.mail","action":"Pterodactyl\Http\Controllers\Admin\Settings\MailController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings\/advanced","name":"admin.settings.advanced","action":"Pterodactyl\Http\Controllers\Admin\Settings\AdvancedController@index"},{"host":null,"methods":["PATCH"],"uri":"admin\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Settings\IndexController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/settings\/mail","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Settings\MailController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/settings\/advanced","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Settings\AdvancedController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users","name":"admin.users","action":"Pterodactyl\Http\Controllers\Admin\UserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/accounts.json","name":"admin.users.json","action":"Pterodactyl\Http\Controllers\Admin\UserController@json"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/new","name":"admin.users.new","action":"Pterodactyl\Http\Controllers\Admin\UserController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/view\/{user}","name":"admin.users.view","action":"Pterodactyl\Http\Controllers\Admin\UserController@view"},{"host":null,"methods":["POST"],"uri":"admin\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@store"},{"host":null,"methods":["PATCH"],"uri":"admin\/users\/view\/{user}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/users\/view\/{user}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers","name":"admin.servers","action":"Pterodactyl\Http\Controllers\Admin\ServersController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/new","name":"admin.servers.new","action":"Pterodactyl\Http\Controllers\Admin\ServersController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}","name":"admin.servers.view","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/details","name":"admin.servers.view.details","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDetails"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/build","name":"admin.servers.view.build","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewBuild"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/startup","name":"admin.servers.view.startup","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/database","name":"admin.servers.view.database","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/manage","name":"admin.servers.view.manage","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewManage"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/delete","name":"admin.servers.view.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDelete"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@store"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/build","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@updateBuild"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@saveStartup"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@newDatabase"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/toggle","name":"admin.servers.view.manage.toggle","action":"Pterodactyl\Http\Controllers\Admin\ServersController@toggleInstall"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/rebuild","name":"admin.servers.view.manage.rebuild","action":"Pterodactyl\Http\Controllers\Admin\ServersController@rebuildContainer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/suspension","name":"admin.servers.view.manage.suspension","action":"Pterodactyl\Http\Controllers\Admin\ServersController@manageSuspension"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/reinstall","name":"admin.servers.view.manage.reinstall","action":"Pterodactyl\Http\Controllers\Admin\ServersController@reinstallServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/delete","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@delete"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/details","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@setDetails"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@resetDatabasePassword"},{"host":null,"methods":["DELETE"],"uri":"admin\/servers\/view\/{server}\/database\/{database}\/delete","name":"admin.servers.view.database.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@deleteDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes","name":"admin.nodes","action":"Pterodactyl\Http\Controllers\Admin\NodesController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/new","name":"admin.nodes.new","action":"Pterodactyl\Http\Controllers\Admin\NodesController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}","name":"admin.nodes.view","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/settings","name":"admin.nodes.view.settings","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewSettings"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/configuration","name":"admin.nodes.view.configuration","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewConfiguration"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/allocation","name":"admin.nodes.view.allocation","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewAllocation"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/servers","name":"admin.nodes.view.servers","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewServers"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/settings\/token","name":"admin.nodes.view.configuration.token","action":"Pterodactyl\Http\Controllers\Admin\NodesController@setToken"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@createAllocation"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation\/remove","name":"admin.nodes.view.allocation.removeBlock","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveBlock"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation\/alias","name":"admin.nodes.view.allocation.setAlias","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationSetAlias"},{"host":null,"methods":["PATCH"],"uri":"admin\/nodes\/view\/{node}\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@updateSettings"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{node}\/delete","name":"admin.nodes.view.delete","action":"Pterodactyl\Http\Controllers\Admin\NodesController@delete"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{node}\/allocation\/remove\/{allocation}","name":"admin.nodes.view.allocation.removeSingle","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveSingle"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests","name":"admin.nests","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/new","name":"admin.nests.new","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/view\/{nest}","name":"admin.nests.view","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/new","name":"admin.nests.egg.new","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}","name":"admin.nests.egg.view","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/export","name":"admin.nests.egg.export","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@export"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/variables","name":"admin.nests.egg.variables","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/scripts","name":"admin.nests.egg.scripts","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggScriptController@index"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/import","name":"admin.nests.egg.import","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@import"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/egg\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/egg\/{egg}\/variables","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@store"},{"host":null,"methods":["PUT"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/view\/{nest}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}\/scripts","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggScriptController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}\/variables\/{variable}","name":"admin.nests.egg.variables.edit","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/view\/{nest}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@destroy"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@destroy"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/egg\/{egg}\/variables\/{variable}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@destroy"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs","name":"admin.packs","action":"Pterodactyl\Http\Controllers\Admin\PackController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new","name":"admin.packs.new","action":"Pterodactyl\Http\Controllers\Admin\PackController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new\/template","name":"admin.packs.new.template","action":"Pterodactyl\Http\Controllers\Admin\PackController@newTemplate"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/view\/{pack}","name":"admin.packs.view","action":"Pterodactyl\Http\Controllers\Admin\PackController@view"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@store"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/view\/{pack}\/export\/{files?}","name":"admin.packs.view.export","action":"Pterodactyl\Http\Controllers\Admin\PackController@export"},{"host":null,"methods":["PATCH"],"uri":"admin\/packs\/view\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/packs\/view\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@destroy"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login","name":"auth.login","action":"Pterodactyl\Http\Controllers\Auth\LoginController@showLoginForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login\/totp","name":"auth.totp","action":"Pterodactyl\Http\Controllers\Auth\LoginController@totp"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password","name":"auth.password","action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password\/reset\/{token}","name":"auth.reset","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@showResetForm"},{"host":null,"methods":["POST"],"uri":"auth\/login","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@login"},{"host":null,"methods":["POST"],"uri":"auth\/login\/totp","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@loginUsingTotp"},{"host":null,"methods":["POST"],"uri":"auth\/password","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset","name":"auth.reset.post","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@reset"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset\/{token}","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/logout","name":"auth.logout","action":"Pterodactyl\Http\Controllers\Auth\LoginController@logout"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}","name":"server.index","action":"Pterodactyl\Http\Controllers\Server\ConsoleController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/console","name":"server.console","action":"Pterodactyl\Http\Controllers\Server\ConsoleController@console"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/allocation","name":"server.settings.allocation","action":"Pterodactyl\Http\Controllers\Server\Settings\AllocationController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/settings\/allocation","name":null,"action":"Pterodactyl\Http\Controllers\Server\Settings\AllocationController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/sftp","name":"server.settings.sftp","action":"Pterodactyl\Http\Controllers\Server\Settings\SftpController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/startup","name":"server.settings.startup","action":"Pterodactyl\Http\Controllers\Server\Settings\StartupController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/settings\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Server\Settings\StartupController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/databases","name":"server.databases.index","action":"Pterodactyl\Http\Controllers\Server\DatabaseController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/databases\/password","name":"server.databases.password","action":"Pterodactyl\Http\Controllers\Server\DatabaseController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files","name":"server.files.index","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/add","name":"server.files.add","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/edit\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/download\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\Files\DownloadController@index"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/directory-list","name":"server.files.directory-list","action":"Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController@directory"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/save","name":"server.files.save","action":"Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users","name":"server.subusers","action":"Pterodactyl\Http\Controllers\Server\SubuserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/new","name":"server.subusers.new","action":"Pterodactyl\Http\Controllers\Server\SubuserController@create"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/view\/{subuser}","name":"server.subusers.view","action":"Pterodactyl\Http\Controllers\Server\SubuserController@view"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/users\/view\/{subuser}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@update"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/users\/view\/{subuser}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules","name":"server.schedules","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules\/new","name":"server.schedules.new","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@create"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/schedules\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":"server.schedules.view","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@view"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@update"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/schedules\/view\/{schedule}\/toggle","name":"server.schedules.toggle","action":"Pterodactyl\Http\Controllers\Server\Tasks\ActionController@toggle"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/schedules\/view\/{schedule}\/trigger","name":"server.schedules.trigger","action":"Pterodactyl\Http\Controllers\Server\Tasks\ActionController@trigger"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/authenticate\/{token}","name":"api.remote.authenticate","action":"Pterodactyl\Http\Controllers\API\Remote\ValidateKeyController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/eggs","name":"api.remote.eggs","action":"Pterodactyl\Http\Controllers\API\Remote\EggRetrievalController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/eggs\/{uuid}","name":"api.remote.eggs.download","action":"Pterodactyl\Http\Controllers\API\Remote\EggRetrievalController@download"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/scripts\/{uuid}","name":"api.remote.scripts","action":"Pterodactyl\Http\Controllers\API\Remote\EggInstallController@index"},{"host":null,"methods":["POST"],"uri":"api\/remote\/sftp","name":"api.remote.sftp","action":"Pterodactyl\Http\Controllers\API\Remote\SftpController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}","name":"daemon.pack.pull","action":"Pterodactyl\Http\Controllers\Daemon\PackController@pull"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}\/hash","name":"daemon.pack.hash","action":"Pterodactyl\Http\Controllers\Daemon\PackController@hash"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/configure\/{token}","name":"daemon.configuration","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@configuration"},{"host":null,"methods":["POST"],"uri":"daemon\/download","name":"daemon.download","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@authenticateDownload"},{"host":null,"methods":["POST"],"uri":"daemon\/install","name":"daemon.install","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@markInstall"}], + rootUrl: 'http://pterodactyl.local', + routes : [{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/open","name":"debugbar.openhandler","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@handle"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/clockwork\/{id}","name":"debugbar.clockwork","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@clockwork"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/stylesheets","name":"debugbar.assets.css","action":"Barryvdh\Debugbar\Controllers\AssetController@css"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/javascript","name":"debugbar.assets.js","action":"Barryvdh\Debugbar\Controllers\AssetController@js"},{"host":null,"methods":["GET","HEAD"],"uri":"\/","name":"index","action":"Pterodactyl\Http\Controllers\Base\IndexController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"status\/{server}","name":"index.status","action":"Pterodactyl\Http\Controllers\Base\IndexController@status"},{"host":null,"methods":["GET","HEAD"],"uri":"account","name":"account","action":"Pterodactyl\Http\Controllers\Base\AccountController@index"},{"host":null,"methods":["POST"],"uri":"account","name":null,"action":"Pterodactyl\Http\Controllers\Base\AccountController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api","name":"account.api","action":"Pterodactyl\Http\Controllers\Base\AccountKeyController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api\/new","name":"account.api.new","action":"Pterodactyl\Http\Controllers\Base\AccountKeyController@create"},{"host":null,"methods":["POST"],"uri":"account\/api\/new","name":null,"action":"Pterodactyl\Http\Controllers\Base\AccountKeyController@store"},{"host":null,"methods":["DELETE"],"uri":"account\/api\/revoke\/{identifier}","name":"account.api.revoke","action":"Pterodactyl\Http\Controllers\Base\AccountKeyController@revoke"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security","name":"account.security","action":"Pterodactyl\Http\Controllers\Base\SecurityController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security\/revoke\/{id}","name":"account.security.revoke","action":"Pterodactyl\Http\Controllers\Base\SecurityController@revoke"},{"host":null,"methods":["PUT"],"uri":"account\/security\/totp","name":"account.security.totp","action":"Pterodactyl\Http\Controllers\Base\SecurityController@generateTotp"},{"host":null,"methods":["POST"],"uri":"account\/security\/totp","name":"account.security.totp.set","action":"Pterodactyl\Http\Controllers\Base\SecurityController@setTotp"},{"host":null,"methods":["DELETE"],"uri":"account\/security\/totp","name":"account.security.totp.disable","action":"Pterodactyl\Http\Controllers\Base\SecurityController@disableTotp"},{"host":null,"methods":["GET","HEAD"],"uri":"admin","name":"admin.index","action":"Pterodactyl\Http\Controllers\Admin\BaseController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations","name":"admin.locations","action":"Pterodactyl\Http\Controllers\Admin\LocationController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations\/view\/{location}","name":"admin.locations.view","action":"Pterodactyl\Http\Controllers\Admin\LocationController@view"},{"host":null,"methods":["POST"],"uri":"admin\/locations","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@create"},{"host":null,"methods":["PATCH"],"uri":"admin\/locations\/view\/{location}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases","name":"admin.databases","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases\/view\/{host}","name":"admin.databases.view","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@view"},{"host":null,"methods":["POST"],"uri":"admin\/databases","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@create"},{"host":null,"methods":["PATCH"],"uri":"admin\/databases\/view\/{host}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/databases\/view\/{host}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings","name":"admin.settings","action":"Pterodactyl\Http\Controllers\Admin\Settings\IndexController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings\/mail","name":"admin.settings.mail","action":"Pterodactyl\Http\Controllers\Admin\Settings\MailController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings\/advanced","name":"admin.settings.advanced","action":"Pterodactyl\Http\Controllers\Admin\Settings\AdvancedController@index"},{"host":null,"methods":["PATCH"],"uri":"admin\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Settings\IndexController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/settings\/mail","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Settings\MailController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/settings\/advanced","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Settings\AdvancedController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users","name":"admin.users","action":"Pterodactyl\Http\Controllers\Admin\UserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/accounts.json","name":"admin.users.json","action":"Pterodactyl\Http\Controllers\Admin\UserController@json"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/new","name":"admin.users.new","action":"Pterodactyl\Http\Controllers\Admin\UserController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/view\/{user}","name":"admin.users.view","action":"Pterodactyl\Http\Controllers\Admin\UserController@view"},{"host":null,"methods":["POST"],"uri":"admin\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@store"},{"host":null,"methods":["PATCH"],"uri":"admin\/users\/view\/{user}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/users\/view\/{user}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers","name":"admin.servers","action":"Pterodactyl\Http\Controllers\Admin\ServersController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/new","name":"admin.servers.new","action":"Pterodactyl\Http\Controllers\Admin\ServersController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}","name":"admin.servers.view","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/details","name":"admin.servers.view.details","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDetails"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/build","name":"admin.servers.view.build","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewBuild"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/startup","name":"admin.servers.view.startup","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/database","name":"admin.servers.view.database","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/manage","name":"admin.servers.view.manage","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewManage"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/delete","name":"admin.servers.view.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDelete"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@store"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/build","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@updateBuild"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@saveStartup"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@newDatabase"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/toggle","name":"admin.servers.view.manage.toggle","action":"Pterodactyl\Http\Controllers\Admin\ServersController@toggleInstall"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/rebuild","name":"admin.servers.view.manage.rebuild","action":"Pterodactyl\Http\Controllers\Admin\ServersController@rebuildContainer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/suspension","name":"admin.servers.view.manage.suspension","action":"Pterodactyl\Http\Controllers\Admin\ServersController@manageSuspension"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/reinstall","name":"admin.servers.view.manage.reinstall","action":"Pterodactyl\Http\Controllers\Admin\ServersController@reinstallServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/delete","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@delete"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/details","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@setDetails"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@resetDatabasePassword"},{"host":null,"methods":["DELETE"],"uri":"admin\/servers\/view\/{server}\/database\/{database}\/delete","name":"admin.servers.view.database.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@deleteDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes","name":"admin.nodes","action":"Pterodactyl\Http\Controllers\Admin\NodesController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/new","name":"admin.nodes.new","action":"Pterodactyl\Http\Controllers\Admin\NodesController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}","name":"admin.nodes.view","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/settings","name":"admin.nodes.view.settings","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewSettings"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/configuration","name":"admin.nodes.view.configuration","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewConfiguration"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/allocation","name":"admin.nodes.view.allocation","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewAllocation"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/servers","name":"admin.nodes.view.servers","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewServers"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/settings\/token","name":"admin.nodes.view.configuration.token","action":"Pterodactyl\Http\Controllers\Admin\NodesController@setToken"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@createAllocation"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation\/remove","name":"admin.nodes.view.allocation.removeBlock","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveBlock"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation\/alias","name":"admin.nodes.view.allocation.setAlias","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationSetAlias"},{"host":null,"methods":["PATCH"],"uri":"admin\/nodes\/view\/{node}\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@updateSettings"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{node}\/delete","name":"admin.nodes.view.delete","action":"Pterodactyl\Http\Controllers\Admin\NodesController@delete"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{node}\/allocation\/remove\/{allocation}","name":"admin.nodes.view.allocation.removeSingle","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveSingle"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests","name":"admin.nests","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/new","name":"admin.nests.new","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/view\/{nest}","name":"admin.nests.view","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/new","name":"admin.nests.egg.new","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}","name":"admin.nests.egg.view","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/export","name":"admin.nests.egg.export","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@export"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/variables","name":"admin.nests.egg.variables","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/scripts","name":"admin.nests.egg.scripts","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggScriptController@index"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/import","name":"admin.nests.egg.import","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@import"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/egg\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/egg\/{egg}\/variables","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@store"},{"host":null,"methods":["PUT"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/view\/{nest}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}\/scripts","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggScriptController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}\/variables\/{variable}","name":"admin.nests.egg.variables.edit","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/view\/{nest}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@destroy"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@destroy"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/egg\/{egg}\/variables\/{variable}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@destroy"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs","name":"admin.packs","action":"Pterodactyl\Http\Controllers\Admin\PackController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new","name":"admin.packs.new","action":"Pterodactyl\Http\Controllers\Admin\PackController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new\/template","name":"admin.packs.new.template","action":"Pterodactyl\Http\Controllers\Admin\PackController@newTemplate"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/view\/{pack}","name":"admin.packs.view","action":"Pterodactyl\Http\Controllers\Admin\PackController@view"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@store"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/view\/{pack}\/export\/{files?}","name":"admin.packs.view.export","action":"Pterodactyl\Http\Controllers\Admin\PackController@export"},{"host":null,"methods":["PATCH"],"uri":"admin\/packs\/view\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/packs\/view\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@destroy"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login","name":"auth.login","action":"Pterodactyl\Http\Controllers\Auth\LoginController@showLoginForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login\/totp","name":"auth.totp","action":"Pterodactyl\Http\Controllers\Auth\LoginController@totp"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password","name":"auth.password","action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password\/reset\/{token}","name":"auth.reset","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@showResetForm"},{"host":null,"methods":["POST"],"uri":"auth\/login","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@login"},{"host":null,"methods":["POST"],"uri":"auth\/login\/totp","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@loginUsingTotp"},{"host":null,"methods":["POST"],"uri":"auth\/password","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset","name":"auth.reset.post","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@reset"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset\/{token}","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/logout","name":"auth.logout","action":"Pterodactyl\Http\Controllers\Auth\LoginController@logout"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}","name":"server.index","action":"Pterodactyl\Http\Controllers\Server\ConsoleController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/console","name":"server.console","action":"Pterodactyl\Http\Controllers\Server\ConsoleController@console"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/allocation","name":"server.settings.allocation","action":"Pterodactyl\Http\Controllers\Server\Settings\AllocationController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/settings\/allocation","name":null,"action":"Pterodactyl\Http\Controllers\Server\Settings\AllocationController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/sftp","name":"server.settings.sftp","action":"Pterodactyl\Http\Controllers\Server\Settings\SftpController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/startup","name":"server.settings.startup","action":"Pterodactyl\Http\Controllers\Server\Settings\StartupController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/settings\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Server\Settings\StartupController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/databases","name":"server.databases.index","action":"Pterodactyl\Http\Controllers\Server\DatabaseController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/databases\/password","name":"server.databases.password","action":"Pterodactyl\Http\Controllers\Server\DatabaseController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files","name":"server.files.index","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/add","name":"server.files.add","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/edit\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/download\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\Files\DownloadController@index"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/directory-list","name":"server.files.directory-list","action":"Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController@directory"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/save","name":"server.files.save","action":"Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users","name":"server.subusers","action":"Pterodactyl\Http\Controllers\Server\SubuserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/new","name":"server.subusers.new","action":"Pterodactyl\Http\Controllers\Server\SubuserController@create"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/view\/{subuser}","name":"server.subusers.view","action":"Pterodactyl\Http\Controllers\Server\SubuserController@view"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/users\/view\/{subuser}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@update"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/users\/view\/{subuser}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules","name":"server.schedules","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules\/new","name":"server.schedules.new","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@create"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/schedules\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":"server.schedules.view","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@view"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@update"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/schedules\/view\/{schedule}\/toggle","name":"server.schedules.toggle","action":"Pterodactyl\Http\Controllers\Server\Tasks\ActionController@toggle"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/schedules\/view\/{schedule}\/trigger","name":"server.schedules.trigger","action":"Pterodactyl\Http\Controllers\Server\Tasks\ActionController@trigger"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/users","name":"api.admin.user.list","action":"Pterodactyl\Http\Controllers\API\Admin\Users\UserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/users\/{user}","name":"api.admin.user.view","action":"Pterodactyl\Http\Controllers\API\Admin\Users\UserController@view"},{"host":null,"methods":["POST"],"uri":"api\/admin\/users","name":"api.admin.user.store","action":"Pterodactyl\Http\Controllers\API\Admin\Users\UserController@store"},{"host":null,"methods":["PATCH"],"uri":"api\/admin\/users\/{user}","name":"api.admin.user.update","action":"Pterodactyl\Http\Controllers\API\Admin\Users\UserController@update"},{"host":null,"methods":["DELETE"],"uri":"api\/admin\/users\/{user}","name":"api.admin.user.delete","action":"Pterodactyl\Http\Controllers\API\Admin\Users\UserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/nodes","name":"api.admin.node.list","action":"Pterodactyl\Http\Controllers\API\Admin\Nodes\NodeController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/nodes\/{node}","name":"api.admin.node.view","action":"Pterodactyl\Http\Controllers\API\Admin\Nodes\NodeController@view"},{"host":null,"methods":["POST"],"uri":"api\/admin\/nodes","name":"api.admin.node.store","action":"Pterodactyl\Http\Controllers\API\Admin\Nodes\NodeController@store"},{"host":null,"methods":["PATCH"],"uri":"api\/admin\/nodes\/{node}","name":"api.admin.node.update","action":"Pterodactyl\Http\Controllers\API\Admin\Nodes\NodeController@update"},{"host":null,"methods":["DELETE"],"uri":"api\/admin\/nodes\/{node}","name":"api.admin.node.delete","action":"Pterodactyl\Http\Controllers\API\Admin\Nodes\NodeController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/nodes\/{node}\/allocations","name":"api.admin.node.allocations.list","action":"Pterodactyl\Http\Controllers\API\Admin\Nodes\AllocationController@index"},{"host":null,"methods":["DELETE"],"uri":"api\/admin\/nodes\/{node}\/allocations\/{allocation}","name":"api.admin.node.allocations.delete","action":"Pterodactyl\Http\Controllers\API\Admin\Nodes\AllocationController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/locations","name":"api.admin.location.list","action":"Pterodactyl\Http\Controllers\API\Admin\Locations\LocationController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/locations\/{location}","name":"api.admin.location.view","action":"Pterodactyl\Http\Controllers\API\Admin\Locations\LocationController@view"},{"host":null,"methods":["POST"],"uri":"api\/admin\/locations","name":"api.admin.location.store","action":"Pterodactyl\Http\Controllers\API\Admin\Locations\LocationController@store"},{"host":null,"methods":["PATCH"],"uri":"api\/admin\/locations\/{location}","name":"api.admin.location.update","action":"Pterodactyl\Http\Controllers\API\Admin\Locations\LocationController@update"},{"host":null,"methods":["DELETE"],"uri":"api\/admin\/locations\/{location}","name":"api.admin.location.delete","action":"Pterodactyl\Http\Controllers\API\Admin\Locations\LocationController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/authenticate\/{token}","name":"api.remote.authenticate","action":"Pterodactyl\Http\Controllers\API\Remote\ValidateKeyController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/eggs","name":"api.remote.eggs","action":"Pterodactyl\Http\Controllers\API\Remote\EggRetrievalController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/eggs\/{uuid}","name":"api.remote.eggs.download","action":"Pterodactyl\Http\Controllers\API\Remote\EggRetrievalController@download"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/scripts\/{uuid}","name":"api.remote.scripts","action":"Pterodactyl\Http\Controllers\API\Remote\EggInstallController@index"},{"host":null,"methods":["POST"],"uri":"api\/remote\/sftp","name":"api.remote.sftp","action":"Pterodactyl\Http\Controllers\API\Remote\SftpController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}","name":"daemon.pack.pull","action":"Pterodactyl\Http\Controllers\Daemon\PackController@pull"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}\/hash","name":"daemon.pack.hash","action":"Pterodactyl\Http\Controllers\Daemon\PackController@hash"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/configure\/{token}","name":"daemon.configuration","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@configuration"},{"host":null,"methods":["POST"],"uri":"daemon\/download","name":"daemon.download","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@authenticateDownload"},{"host":null,"methods":["POST"],"uri":"daemon\/install","name":"daemon.install","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@markInstall"}], prefix: '', route : function (name, parameters, route) { diff --git a/resources/lang/en/base.php b/resources/lang/en/base.php index 579ac3410..ba63139d3 100644 --- a/resources/lang/en/base.php +++ b/resources/lang/en/base.php @@ -29,183 +29,25 @@ return [ ], 'api' => [ 'index' => [ - 'header' => 'API Access', - 'header_sub' => 'Manage your API access keys.', - 'list' => 'API Keys', + 'list' => 'Your Keys', + 'header' => 'Accout API', + 'header_sub' => 'Manage access keys that allow you to perform actions aganist the panel.', 'create_new' => 'Create New API key', 'keypair_created' => 'An API key has been successfully generated and is listed below.', ], 'new' => [ 'header' => 'New API Key', - 'header_sub' => 'Create a new API access key', + 'header_sub' => 'Create a new account access key.', 'form_title' => 'Details', 'descriptive_memo' => [ - 'title' => 'Descriptive Memo', - 'description' => 'Enter a brief description of what this API key will be used for.', + 'title' => 'Description', + 'description' => 'Enter a brief description of this key that will be useful for reference.', ], 'allowed_ips' => [ 'title' => 'Allowed IPs', 'description' => 'Enter a line delimitated list of IPs that are allowed to access the API using this key. CIDR notation is allowed. Leave blank to allow any IP.', ], ], - 'permissions' => [ - 'user' => [ - 'server_header' => 'User Server Permissions', - 'server' => [ - 'list' => [ - 'title' => 'List Servers', - 'desc' => 'Allows listing of all servers a user owns or has access to as a subuser.', - ], - 'view' => [ - 'title' => 'View Server', - 'desc' => 'Allows viewing of specific server user can access.', - ], - 'power' => [ - 'title' => 'Toggle Power', - 'desc' => 'Allow toggling of power status for a server.', - ], - 'command' => [ - 'title' => 'Send Command', - 'desc' => 'Allow sending of a command to a running server.', - ], - ], - ], - 'admin' => [ - 'server_header' => 'Server Control', - 'server' => [ - 'list' => [ - 'title' => 'List Servers', - 'desc' => 'Allows listing of all servers currently on the system.', - ], - 'view' => [ - 'title' => 'View Server', - 'desc' => 'Allows view of single server including service and details.', - ], - 'delete' => [ - 'title' => 'Delete Server', - 'desc' => 'Allows deletion of a server from the system.', - ], - 'create' => [ - 'title' => 'Create Server', - 'desc' => 'Allows creation of a new server on the system.', - ], - 'edit-details' => [ - 'title' => 'Edit Server Details', - 'desc' => 'Allows editing of server details such as name, owner, description, and secret key.', - ], - 'edit-container' => [ - 'title' => 'Edit Server Container', - 'desc' => 'Allows for modification of the docker container the server runs in.', - ], - 'suspend' => [ - 'title' => 'Suspend Server', - 'desc' => 'Allows for the suspension and unsuspension of a given server.', - ], - 'install' => [ - 'title' => 'Toggle Install Status', - 'desc' => '', - ], - 'rebuild' => [ - 'title' => 'Rebuild Server', - 'desc' => '', - ], - 'edit-build' => [ - 'title' => 'Edit Server Build', - 'desc' => 'Allows editing of server build setting such as CPU and memory allocations.', - ], - 'edit-startup' => [ - 'title' => 'Edit Server Startup', - 'desc' => 'Allows modification of server startup commands and parameters.', - ], - ], - 'location_header' => 'Location Control', - 'location' => [ - 'list' => [ - 'title' => 'List Locations', - 'desc' => 'Allows listing all locations and thier associated nodes.', - ], - ], - 'node_header' => 'Node Control', - 'node' => [ - 'list' => [ - 'title' => 'List Nodes', - 'desc' => 'Allows listing of all nodes currently on the system.', - ], - 'view' => [ - 'title' => 'View Node', - 'desc' => 'Allows viewing details about a specific node including active services.', - ], - 'view-config' => [ - 'title' => 'View Node Configuration', - 'desc' => 'Danger. This allows the viewing of the node configuration file used by the daemon, and exposes secret daemon tokens.', - ], - 'create' => [ - 'title' => 'Create Node', - 'desc' => 'Allows creating a new node on the system.', - ], - 'delete' => [ - 'title' => 'Delete Node', - 'desc' => 'Allows deletion of a node from the system.', - ], - ], - 'user_header' => 'User Control', - 'user' => [ - 'list' => [ - 'title' => 'List Users', - 'desc' => 'Allows listing of all users currently on the system.', - ], - 'view' => [ - 'title' => 'View User', - 'desc' => 'Allows viewing details about a specific user including active services.', - ], - 'create' => [ - 'title' => 'Create User', - 'desc' => 'Allows creating a new user on the system.', - ], - 'edit' => [ - 'title' => 'Update User', - 'desc' => 'Allows modification of user details.', - ], - 'delete' => [ - 'title' => 'Delete User', - 'desc' => 'Allows deleting a user.', - ], - ], - 'service_header' => 'Service Control', - 'service' => [ - 'list' => [ - 'title' => 'List Service', - 'desc' => 'Allows listing of all services configured on the system.', - ], - 'view' => [ - 'title' => 'View Service', - 'desc' => 'Allows listing details about each service on the system including service options and variables.', - ], - ], - 'option_header' => 'Option Control', - 'option' => [ - 'list' => [ - 'title' => 'List Options', - 'desc' => '', - ], - 'view' => [ - 'title' => 'View Option', - 'desc' => '', - ], - ], - 'pack_header' => 'Pack Control', - 'pack' => [ - 'list' => [ - 'title' => 'List Packs', - 'desc' => '', - ], - 'view' => [ - 'title' => 'View Pack', - 'desc' => '', - ], - ], - ], - ], ], 'account' => [ 'details_updated' => 'Your account details have been successfully updated.', diff --git a/resources/lang/en/navigation.php b/resources/lang/en/navigation.php index b8f2fb83a..f8a9deebb 100644 --- a/resources/lang/en/navigation.php +++ b/resources/lang/en/navigation.php @@ -6,7 +6,7 @@ return [ 'header' => 'ACCOUNT MANAGEMENT', 'my_account' => 'My Account', 'security_controls' => 'Security Controls', - 'api_access' => 'API Access', + 'api_access' => 'Account API', 'my_servers' => 'My Servers', ], 'server' => [ diff --git a/resources/themes/pterodactyl/base/api/index.blade.php b/resources/themes/pterodactyl/base/api/index.blade.php index 1a6dd0a72..e21e5aecc 100644 --- a/resources/themes/pterodactyl/base/api/index.blade.php +++ b/resources/themes/pterodactyl/base/api/index.blade.php @@ -52,7 +52,7 @@ @datetimeHuman($key->created_at) - + @endforeach @@ -84,7 +84,7 @@ }, function () { $.ajax({ method: 'DELETE', - url: Router.route('account.api.revoke', { key: self.data('attr') }), + url: Router.route('account.api.revoke', { identifier: self.data('attr') }), headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' } diff --git a/resources/themes/pterodactyl/base/api/new.blade.php b/resources/themes/pterodactyl/base/api/new.blade.php index 0551feb94..4c2da25eb 100644 --- a/resources/themes/pterodactyl/base/api/new.blade.php +++ b/resources/themes/pterodactyl/base/api/new.blade.php @@ -49,13 +49,7 @@
-
- -
-
+
{!! csrf_field() !!}
diff --git a/routes/base.php b/routes/base.php index 674e6ad6d..c66b33e90 100644 --- a/routes/base.php +++ b/routes/base.php @@ -32,12 +32,12 @@ Route::group(['prefix' => 'account'], function () { | */ Route::group(['prefix' => 'account/api'], function () { - Route::get('/', 'APIController@index')->name('account.api'); - Route::get('/new', 'APIController@create')->name('account.api.new'); + Route::get('/', 'AccountKeyController@index')->name('account.api'); + Route::get('/new', 'AccountKeyController@create')->name('account.api.new'); - Route::post('/new', 'APIController@store'); + Route::post('/new', 'AccountKeyController@store'); - Route::delete('/revoke/{key}', 'APIController@revoke')->name('account.api.revoke'); + Route::delete('/revoke/{identifier}', 'AccountKeyController@revoke')->name('account.api.revoke'); }); /* diff --git a/tests/TestCase.php b/tests/TestCase.php index 5f9ba7482..427744f71 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,6 +2,7 @@ namespace Tests; +use Cake\Chronos\Chronos; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; abstract class TestCase extends BaseTestCase @@ -18,6 +19,16 @@ abstract class TestCase extends BaseTestCase $this->setKnownUuidFactory(); } + /** + * Tear down tests. + */ + protected function tearDown() + { + parent::tearDown(); + + Chronos::setTestNow(); + } + /** * Handles the known UUID handling in certain unit tests. Use the "KnownUuid" trait * in order to enable this ability. diff --git a/tests/Unit/Http/Controllers/Base/APIControllerTest.php b/tests/Unit/Http/Controllers/Base/AccountKeyControllerTest.php similarity index 58% rename from tests/Unit/Http/Controllers/Base/APIControllerTest.php rename to tests/Unit/Http/Controllers/Base/AccountKeyControllerTest.php index b2ccf9bfc..8bb5591b3 100644 --- a/tests/Unit/Http/Controllers/Base/APIControllerTest.php +++ b/tests/Unit/Http/Controllers/Base/AccountKeyControllerTest.php @@ -3,16 +3,15 @@ namespace Tests\Unit\Http\Controllers\Base; use Mockery as m; -use Pterodactyl\Models\User; use Pterodactyl\Models\ApiKey; use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Services\Api\KeyCreationService; use Tests\Unit\Http\Controllers\ControllerTestCase; -use Pterodactyl\Http\Controllers\Base\APIController; -use Pterodactyl\Http\Requests\Base\ApiKeyFormRequest; +use Pterodactyl\Http\Requests\Base\StoreAccountKeyRequest; +use Pterodactyl\Http\Controllers\Base\AccountKeyController; use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface; -class APIControllerTest extends ControllerTestCase +class AccountKeyControllerTest extends ControllerTestCase { /** * @var \Prologue\Alerts\AlertsMessageBag|\Mockery\Mock @@ -48,7 +47,7 @@ class APIControllerTest extends ControllerTestCase { $model = $this->generateRequestUserModel(); - $this->repository->shouldReceive('findWhere')->with([['user_id', '=', $model->id]])->once()->andReturn(collect(['testkeys'])); + $this->repository->shouldReceive('getAccountKeys')->with($model)->once()->andReturn(collect(['testkeys'])); $response = $this->getController()->index($this->request); $this->assertIsViewResponse($response); @@ -59,51 +58,34 @@ class APIControllerTest extends ControllerTestCase /** * Test the create API view controller. - * - * @dataProvider rootAdminDataProvider */ - public function testCreateController($admin) + public function testCreateController() { - $this->generateRequestUserModel(['root_admin' => $admin]); + $this->generateRequestUserModel(); $response = $this->getController()->create($this->request); $this->assertIsViewResponse($response); - $this->assertViewNameEquals('base.api.new', $response); - $this->assertViewHasKey('permissions.user', $response); - $this->assertViewHasKey('permissions.admin', $response); - - if ($admin) { - $this->assertViewKeyNotEquals('permissions.admin', null, $response); - } else { - $this->assertViewKeyEquals('permissions.admin', null, $response); - } } /** - * Test the store functionality for a non-admin user. - * - * @dataProvider rootAdminDataProvider + * Test the store functionality for a user. */ - public function testStoreController($admin) + public function testStoreController() { - $this->setRequestMockClass(ApiKeyFormRequest::class); - $model = $this->generateRequestUserModel(['root_admin' => $admin]); + $this->setRequestMockClass(StoreAccountKeyRequest::class); + $model = $this->generateRequestUserModel(); $keyModel = factory(ApiKey::class)->make(); - if ($admin) { - $this->request->shouldReceive('input')->with('admin_permissions', [])->once()->andReturn(['admin.permission']); - } - $this->request->shouldReceive('user')->withNoArgs()->andReturn($model); $this->request->shouldReceive('input')->with('allowed_ips')->once()->andReturnNull(); $this->request->shouldReceive('input')->with('memo')->once()->andReturnNull(); - $this->request->shouldReceive('input')->with('permissions', [])->once()->andReturn(['test.permission']); + $this->keyService->shouldReceive('setKeyType')->with(ApiKey::TYPE_ACCOUNT)->once()->andReturnSelf(); $this->keyService->shouldReceive('handle')->with([ 'user_id' => $model->id, 'allowed_ips' => null, 'memo' => null, - ], ['test.permission'], ($admin) ? ['admin.permission'] : [])->once()->andReturn($keyModel); + ])->once()->andReturn($keyModel); $this->alert->shouldReceive('success')->with(trans('base.api.index.keypair_created'))->once()->andReturnSelf(); $this->alert->shouldReceive('flash')->withNoArgs()->once()->andReturnNull(); @@ -120,34 +102,21 @@ class APIControllerTest extends ControllerTestCase { $model = $this->generateRequestUserModel(); - $this->repository->shouldReceive('deleteWhere')->with([ - ['user_id', '=', $model->id], - ['token', '=', 'testKey123'], - ])->once()->andReturn(1); + $this->repository->shouldReceive('deleteAccountKey')->with($model, 'testIdentifier')->once()->andReturn(1); - $response = $this->getController()->revoke($this->request, 'testKey123'); + $response = $this->getController()->revoke($this->request, 'testIdentifier'); $this->assertIsResponse($response); $this->assertEmpty($response->getContent()); $this->assertResponseCodeEquals(204, $response); } - /** - * Data provider to determine if a user is a root admin. - * - * @return array - */ - public function rootAdminDataProvider() - { - return [[0], [1]]; - } - /** * Return an instance of the controller with mocked dependencies for testing. * - * @return \Pterodactyl\Http\Controllers\Base\APIController + * @return \Pterodactyl\Http\Controllers\Base\AccountKeyController */ - private function getController(): APIController + private function getController(): AccountKeyController { - return new APIController($this->alert, $this->repository, $this->keyService); + return new AccountKeyController($this->alert, $this->repository, $this->keyService); } } diff --git a/tests/Unit/Http/Middleware/Api/Admin/AuthenticateKeyTest.php b/tests/Unit/Http/Middleware/Api/Admin/AuthenticateKeyTest.php index be2237588..e0de17a45 100644 --- a/tests/Unit/Http/Middleware/Api/Admin/AuthenticateKeyTest.php +++ b/tests/Unit/Http/Middleware/Api/Admin/AuthenticateKeyTest.php @@ -1,8 +1,9 @@ auth = m::mock(AuthManager::class); $this->encrypter = m::mock(Encrypter::class); @@ -77,10 +79,17 @@ class AuthenticateKeyTest extends MiddlewareTestCase $model = factory(ApiKey::class)->make(); $this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn($model->identifier . 'decrypted'); - $this->repository->shouldReceive('findFirstWhere')->with([['identifier', '=', $model->identifier]])->once()->andReturn($model); + $this->repository->shouldReceive('findFirstWhere')->with([ + ['identifier', '=', $model->identifier], + ['key_type', '=', ApiKey::TYPE_APPLICATION], + ])->once()->andReturn($model); $this->encrypter->shouldReceive('decrypt')->with($model->token)->once()->andReturn('decrypted'); $this->auth->shouldReceive('guard->loginUsingId')->with($model->user_id)->once()->andReturnNull(); + $this->repository->shouldReceive('withoutFreshModel->update')->with($model->id, [ + 'last_used_at' => Chronos::now(), + ])->once()->andReturnNull(); + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); $this->assertEquals($model, $this->request->attributes->get('api_key')); } @@ -96,7 +105,10 @@ class AuthenticateKeyTest extends MiddlewareTestCase $model = factory(ApiKey::class)->make(); $this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn($model->identifier . 'asdf'); - $this->repository->shouldReceive('findFirstWhere')->with([['identifier', '=', $model->identifier]])->once()->andReturn($model); + $this->repository->shouldReceive('findFirstWhere')->with([ + ['identifier', '=', $model->identifier], + ['key_type', '=', ApiKey::TYPE_APPLICATION], + ])->once()->andReturn($model); $this->encrypter->shouldReceive('decrypt')->with($model->token)->once()->andReturn('decrypted'); $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); diff --git a/tests/Unit/Services/Api/KeyCreationServiceTest.php b/tests/Unit/Services/Api/KeyCreationServiceTest.php index 8daed6cda..613be35f8 100644 --- a/tests/Unit/Services/Api/KeyCreationServiceTest.php +++ b/tests/Unit/Services/Api/KeyCreationServiceTest.php @@ -51,6 +51,7 @@ class KeyCreationServiceTest extends TestCase $this->repository->shouldReceive('create')->with([ 'test-data' => 'test', + 'key_type' => ApiKey::TYPE_NONE, 'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH, 'token' => $model->token, ], true, true)->once()->andReturn($model); @@ -62,6 +63,9 @@ class KeyCreationServiceTest extends TestCase $this->assertSame($model, $response); } + /** + * Test that an identifier is only set by the function. + */ public function testIdentifierAndTokenAreOnlySetByFunction() { $model = factory(ApiKey::class)->make(); @@ -74,6 +78,7 @@ class KeyCreationServiceTest extends TestCase $this->encrypter->shouldReceive('encrypt')->with('str_' . ApiKey::KEY_LENGTH)->once()->andReturn($model->token); $this->repository->shouldReceive('create')->with([ + 'key_type' => ApiKey::TYPE_NONE, 'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH, 'token' => $model->token, ], true, true)->once()->andReturn($model); @@ -85,6 +90,75 @@ class KeyCreationServiceTest extends TestCase $this->assertSame($model, $response); } + /** + * Test that permissions passed in are loaded onto the key data. + */ + public function testPermissionsAreRetrievedForApplicationKeys() + { + $model = factory(ApiKey::class)->make(); + + $this->getFunctionMock('\\Pterodactyl\\Services\\Api', 'str_random') + ->expects($this->exactly(2))->willReturnCallback(function ($length) { + return 'str_' . $length; + }); + + $this->encrypter->shouldReceive('encrypt')->with('str_' . ApiKey::KEY_LENGTH)->once()->andReturn($model->token); + + $this->repository->shouldReceive('create')->with([ + 'key_type' => ApiKey::TYPE_APPLICATION, + 'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH, + 'token' => $model->token, + 'permission-key' => 'exists', + ], true, true)->once()->andReturn($model); + + $response = $this->getService()->setKeyType(ApiKey::TYPE_APPLICATION)->handle([], ['permission-key' => 'exists']); + + $this->assertNotEmpty($response); + $this->assertInstanceOf(ApiKey::class, $response); + $this->assertSame($model, $response); + } + + /** + * Test that permissions are not retrieved for any key that is not an application key. + * + * @dataProvider keyTypeDataProvider + */ + public function testPermissionsAreNotRetrievedForNonApplicationKeys($keyType) + { + $model = factory(ApiKey::class)->make(); + + $this->getFunctionMock('\\Pterodactyl\\Services\\Api', 'str_random') + ->expects($this->exactly(2))->willReturnCallback(function ($length) { + return 'str_' . $length; + }); + + $this->encrypter->shouldReceive('encrypt')->with('str_' . ApiKey::KEY_LENGTH)->once()->andReturn($model->token); + + $this->repository->shouldReceive('create')->with([ + 'key_type' => $keyType, + 'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH, + 'token' => $model->token, + ], true, true)->once()->andReturn($model); + + $response = $this->getService()->setKeyType($keyType)->handle([], ['fake-permission' => 'should-not-exist']); + + $this->assertNotEmpty($response); + $this->assertInstanceOf(ApiKey::class, $response); + $this->assertSame($model, $response); + } + + /** + * Provide key types that are not an application specific key. + * + * @return array + */ + public function keyTypeDataProvider(): array + { + return [ + [ApiKey::TYPE_NONE], [ApiKey::TYPE_ACCOUNT], [ApiKey::TYPE_DAEMON_USER], [ApiKey::TYPE_DAEMON_APPLICATION], + ]; + } + /** * Return an instance of the service with mocked dependencies for testing. *