Merge branch 'feature/vuejs' into develop

This commit is contained in:
Dane Everitt 2019-05-01 21:00:16 -07:00
commit ec87330a81
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
235 changed files with 18018 additions and 2770 deletions

View File

@ -1,8 +0,0 @@
{
"presets": ["es2015"],
"compact": true,
"minified": true,
"only": "public/themes/pterodactyl/js/frontend/files/src/*.js",
"sourceMaps": "inline",
"comments": false
}

View File

@ -21,6 +21,12 @@ debconf-set-selections <<< 'mariadb-server-5.5 mysql-server/root_password_again
# actually install
apt-get install -y php7.2 php7.2-cli php7.2-gd php7.2-mysql php7.2-pdo php7.2-mbstring php7.2-tokenizer php7.2-bcmath php7.2-xml php7.2-fpm php7.2-memcached php7.2-curl php7.2-zip php-xdebug mariadb-server nginx curl tar unzip git memcached > /dev/null
echo "Install nodejs and yarn"
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
apt-get -y install nodejs yarn > /dev/null
echo "Install composer"
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

26
.env.dusk Normal file
View File

@ -0,0 +1,26 @@
APP_ENV=local
APP_DEBUG=false
APP_KEY=NDWgIKKi9ovNK1PXZpzfNVSBdfCXGb5i
APP_JWT_KEY=test1234
APP_TIMEZONE=America/Los_Angeles
APP_URL=http://pterodactyl.local
CACHE_DRIVER=file
SESSION_DRIVER=file
HASHIDS_SALT=IqRr0g82tCTeuyxGs8RV
HASHIDS_LENGTH=8
MAIL_DRIVER=log
MAIL_FROM=support@pterodactyl.io
QUEUE_DRIVER=array
APP_SERVICE_AUTHOR=testing@pterodactyl.io
MAIL_FROM_NAME="Pterodactyl Panel"
RECAPTCHA_ENABLED=false
DB_CONNECTION=testing
TESTING_DB_HOST=192.168.1.202
TESTING_DB_DATABASE=panel_test
TESTING_DB_USERNAME=panel_test
TESTING_DB_PASSWORD=Test1234

14
.gitignore vendored
View File

@ -7,15 +7,12 @@ storage/framework/*
/.idea
/nbproject
package-lock.json
composer.lock
node_modules
_ide_helper_models.php
*.log
_ide_helper.php
sami.phar
/.sami
.phpstorm.meta.php
.php_cs.cache
public/assets/*
# For local development with docker
# Remove if we ever put the Dockerfile in the repo
@ -32,3 +29,6 @@ coverage.xml
# Vagrant
*.log
resources/lang/locales.js
resources/assets/pterodactyl/scripts/helpers/ziggy.js
resources/assets/scripts/helpers/ziggy.js

57
BUILDING.md Normal file
View File

@ -0,0 +1,57 @@
# Local Development
Pterodactyl is now powered by Vuejs and Tailwindcss and uses webpack at its core to generate compiled assets. Release
versions of Pterodactyl will include pre-compiled, minified, and hashed assets ready-to-go.
However, if you are interested in running custom themes or making modifications to the Vue files you'll need a build
system in place to generate these compiled assets. To get your environment setup, you'll first need to install at least Nodejs
`8`, and it is _highly_ recommended that you also install [Yarn](https://yarnpkg.com) to manage your `node_modules`.
### Install Dependencies
```bash
yarn install
```
The command above will download all of the dependencies necessary to get Pterodactyl assets building. After that, its as
simple as running the command below to generate assets while you're developing.
```bash
# build the compiled assets for development
yarn run build
# build the assets automatically when files are modified
yarn run watch
```
### Hot Module Reloading
For more advanced users, we also support 'Hot Module Reloading', allowing you to quickly see changes you're making
to the Vue template files without having to reload the page you're on. To Get started with this, you just need
to run the command below.
```bash
PUBLIC_PATH=http://192.168.1.1:8080 yarn run serve --host 192.168.1.1
```
There are two _very important_ parts of this command to take note of and change for your specific environment. The first
is the `--host` flag, which is required and should point to the machine where the `webpack-serve` server will be running.
The second is the `PUBLIC_PATH` environment variable which is the URL pointing to the HMR server and is appended to all of
the asset URLs used in Pterodactyl.
#### Vagrant
If you want to use HMR with our Vagrant image, you can use `yarn run v:serve` as a shortcut for the correct parameters.
In order to have proper file change detection you can use the [`vagrant-notify-forwarder`](https://github.com/mhallin/vagrant-notify-forwarder) to notify file events from the host to the VM.
```sh
vagrant plugin install vagrant-notify-forwarder
vagrant reload
```
### Building for Production
Once you have your files squared away and ready for the live server, you'll be needing to generate compiled, minified, and
hashed assets to push live. To do so, run the command below:
```bash
yarn run build:production
```
This will generate a production ready `bundle.js` and `bundle.css` as well as a `manifest.json` and store them in
the `/public/assets` directory where they can then be access by clients, and read by the Panel.

View File

@ -239,7 +239,7 @@ the response from the server `GET` endpoint.
* Nest and Egg listings now show the associated ID in order to make API requests easier.
* Added star indicators to user listing in Admin CP to indicate users who are set as a root admin.
* Creating a new node will now requires a SSL connection if the Panel is configured to use SSL as well.
* Socketio error messages due to permissions are now rendered correctly in the UI rather than causing a silent failure.
* Connector error messages due to permissions are now rendered correctly in the UI rather than causing a silent failure.
* File manager now supports mass deletion option for files and folders.
* Support for CS:GO as a default service option selection.
* Support for GMOD as a default service option selection.
@ -369,7 +369,7 @@ the response from the server `GET` endpoint.
* Changed 2FA login process to be more secure. Previously authentication checking happened on the 2FA post page, now it happens prior and is passed along to the 2FA page to avoid storing any credentials.
### Added
* Socketio error messages due to permissions are now rendered correctly in the UI rather than causing a silent failure.
* Connector error messages due to permissions are now rendered correctly in the UI rather than causing a silent failure.
## v0.7.0-beta.1 (Derelict Dermodactylus)
### Added

View File

@ -0,0 +1,15 @@
<?php
namespace Pterodactyl\Contracts\Http;
interface ClientPermissionsRequest
{
/**
* Returns the permissions string indicating which permission should be used to
* validate that the authenticated user has permission to perform this action aganist
* the given resource (server).
*
* @return string
*/
public function permission(): string;
}

View File

@ -13,7 +13,7 @@ interface FileRepositoryInterface extends BaseRepositoryInterface
* @param string $path
* @return \stdClass
*
* @throws \GuzzleHttp\Exception\RequestException
* @throws \GuzzleHttp\Exception\TransferException
*/
public function getFileStat(string $path): stdClass;
@ -23,7 +23,7 @@ interface FileRepositoryInterface extends BaseRepositoryInterface
* @param string $path
* @return string
*
* @throws \GuzzleHttp\Exception\RequestException
* @throws \GuzzleHttp\Exception\TransferException
*/
public function getContent(string $path): string;
@ -34,7 +34,7 @@ interface FileRepositoryInterface extends BaseRepositoryInterface
* @param string $content
* @return \Psr\Http\Message\ResponseInterface
*
* @throws \GuzzleHttp\Exception\RequestException
* @throws \GuzzleHttp\Exception\TransferException
*/
public function putContent(string $path, string $content): ResponseInterface;
@ -44,7 +44,7 @@ interface FileRepositoryInterface extends BaseRepositoryInterface
* @param string $path
* @return array
*
* @throws \GuzzleHttp\Exception\RequestException
* @throws \GuzzleHttp\Exception\TransferException
*/
public function getDirectory(string $path): array;
}

View File

@ -268,7 +268,7 @@ class Handler extends ExceptionHandler
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
return response()->json(self::convertToArray($exception), 401);
}
return redirect()->guest(route('auth.login'));

View File

@ -0,0 +1,73 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Client;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Pterodactyl\Services\Users\UserUpdateService;
use Pterodactyl\Transformers\Api\Client\AccountTransformer;
use Pterodactyl\Http\Requests\Api\Client\Account\UpdateEmailRequest;
use Pterodactyl\Http\Requests\Api\Client\Account\UpdatePasswordRequest;
class AccountController extends ClientApiController
{
/**
* @var \Pterodactyl\Services\Users\UserUpdateService
*/
private $updateService;
/**
* AccountController constructor.
*
* @param \Pterodactyl\Services\Users\UserUpdateService $updateService
*/
public function __construct(UserUpdateService $updateService)
{
parent::__construct();
$this->updateService = $updateService;
}
/**
* @param Request $request
* @return array
*/
public function index(Request $request): array
{
return $this->fractal->item($request->user())
->transformWith($this->getTransformer(AccountTransformer::class))
->toArray();
}
/**
* Update the authenticated user's email address.
*
* @param \Pterodactyl\Http\Requests\Api\Client\Account\UpdateEmailRequest $request
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function updateEmail(UpdateEmailRequest $request): Response
{
$this->updateService->handle($request->user(), $request->validated());
return response('', Response::HTTP_CREATED);
}
/**
* Update the authenticated user's password.
*
* @param \Pterodactyl\Http\Requests\Api\Client\Account\UpdatePasswordRequest $request
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function updatePassword(UpdatePasswordRequest $request): Response
{
$this->updateService->handle($request->user(), $request->validated());
return response('', Response::HTTP_CREATED);
}
}

View File

@ -35,7 +35,9 @@ class ClientController extends ClientApiController
*/
public function index(GetServersRequest $request): array
{
$servers = $this->repository->filterUserAccessServers($request->user(), User::FILTER_LEVEL_SUBUSER, config('pterodactyl.paginate.frontend.servers'));
$servers = $this->repository
->setSearchTerm($request->input('query'))
->filterUserAccessServers($request->user(), User::FILTER_LEVEL_ALL);
return $this->fractal->collection($servers)
->transformWith($this->getTransformer(ServerTransformer::class))

View File

@ -0,0 +1,96 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
use Illuminate\Http\Response;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Database;
use Pterodactyl\Transformers\Api\Client\DatabaseTransformer;
use Pterodactyl\Services\Databases\DatabaseManagementService;
use Pterodactyl\Services\Databases\DeployServerDatabaseService;
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
use Pterodactyl\Http\Requests\Api\Client\Servers\Databases\GetDatabasesRequest;
use Pterodactyl\Http\Requests\Api\Client\Servers\Databases\StoreDatabaseRequest;
use Pterodactyl\Http\Requests\Api\Client\Servers\Databases\DeleteDatabaseRequest;
class DatabaseController extends ClientApiController
{
/**
* @var \Pterodactyl\Services\Databases\DeployServerDatabaseService
*/
private $deployDatabaseService;
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface
*/
private $repository;
/**
* @var \Pterodactyl\Services\Databases\DatabaseManagementService
*/
private $managementService;
/**
* DatabaseController constructor.
*
* @param \Pterodactyl\Services\Databases\DatabaseManagementService $managementService
* @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository
* @param \Pterodactyl\Services\Databases\DeployServerDatabaseService $deployDatabaseService
*/
public function __construct(
DatabaseManagementService $managementService,
DatabaseRepositoryInterface $repository,
DeployServerDatabaseService $deployDatabaseService
) {
parent::__construct();
$this->deployDatabaseService = $deployDatabaseService;
$this->repository = $repository;
$this->managementService = $managementService;
}
/**
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Databases\GetDatabasesRequest $request
* @return array
*/
public function index(GetDatabasesRequest $request): array
{
$databases = $this->repository->getDatabasesForServer($request->getModel(Server::class)->id);
return $this->fractal->collection($databases)
->transformWith($this->getTransformer(DatabaseTransformer::class))
->toArray();
}
/**
* Create a new database for the given server and return it.
*
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Databases\StoreDatabaseRequest $request
* @return array
*
* @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException
*/
public function store(StoreDatabaseRequest $request): array
{
$database = $this->deployDatabaseService->handle($request->getModel(Server::class), $request->validated());
return $this->fractal->item($database)
->parseIncludes(['password'])
->transformWith($this->getTransformer(DatabaseTransformer::class))
->toArray();
}
/**
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Databases\DeleteDatabaseRequest $request
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function delete(DeleteDatabaseRequest $request): Response
{
$this->managementService->delete($request->getModel(Database::class)->id);
return Response::create('', Response::HTTP_NO_CONTENT);
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
use Carbon\Carbon;
use Ramsey\Uuid\Uuid;
use Pterodactyl\Models\Server;
use Illuminate\Http\JsonResponse;
use Illuminate\Contracts\Cache\Repository as CacheRepository;
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface;
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\ListFilesRequest;
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\DownloadFileRequest;
class FileController extends ClientApiController
{
/**
* @var \Illuminate\Contracts\Cache\Factory
*/
private $cache;
/**
* @var \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface
*/
private $fileRepository;
/**
* FileController constructor.
*
* @param \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface $fileRepository
* @param \Illuminate\Contracts\Cache\Repository $cache
*/
public function __construct(FileRepositoryInterface $fileRepository, CacheRepository $cache)
{
parent::__construct();
$this->cache = $cache;
$this->fileRepository = $fileRepository;
}
/**
* Returns a listing of files in a given directory.
*
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\ListFilesRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function listDirectory(ListFilesRequest $request): JsonResponse
{
return JsonResponse::create([
'contents' => $this->fileRepository->setServer($request->getModel(Server::class))->getDirectory(
$request->get('directory') ?? '/'
),
]);
}
/**
* Configure a reference to a file to download in the cache so that when the
* user hits the Daemon and it verifies with the Panel they'll actually be able
* to download that file.
*
* Returns the token that needs to be used when downloading the file.
*
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\DownloadFileRequest $request
* @return \Illuminate\Http\JsonResponse
* @throws \Exception
*/
public function download(DownloadFileRequest $request): JsonResponse
{
/** @var \Pterodactyl\Models\Server $server */
$server = $request->getModel(Server::class);
$token = Uuid::uuid4()->toString();
$this->cache->put(
'Server:Downloads:' . $token, ['server' => $server->uuid, 'path' => $request->route()->parameter('file')], Carbon::now()->addMinutes(5)
);
return JsonResponse::create(['token' => $token]);
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
use Pterodactyl\Models\Server;
use Pterodactyl\Transformers\Api\Client\AllocationTransformer;
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\GetNetworkRequest;
class NetworkController extends ClientApiController
{
/**
* @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface
*/
private $repository;
/**
* NetworkController constructor.
*
* @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $repository
*/
public function __construct(AllocationRepositoryInterface $repository)
{
parent::__construct();
$this->repository = $repository;
}
/**
* Lists all of the allocations available to a server and wether or
* not they are currently assigned as the primary for this server.
*
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Network\GetNetworkRequest $request
* @return array
*/
public function index(GetNetworkRequest $request): array
{
$server = $request->getModel(Server::class);
$allocations = $this->repository->findWhere([
['server_id', '=', $server->id],
]);
return $this->fractal->collection($allocations)
->transformWith($this->getTransformer(AllocationTransformer::class))
->toArray();
}
}

View File

@ -0,0 +1,159 @@
<?php
namespace Pterodactyl\Http\Controllers\Auth;
use Illuminate\Http\Request;
use Pterodactyl\Models\User;
use Illuminate\Auth\AuthManager;
use Illuminate\Http\JsonResponse;
use PragmaRX\Google2FA\Google2FA;
use Illuminate\Auth\Events\Failed;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Encryption\Encrypter;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Contracts\Cache\Repository as CacheRepository;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
abstract class AbstractLoginController extends Controller
{
use AuthenticatesUsers;
/**
* @var \Illuminate\Auth\AuthManager
*/
protected $auth;
/**
* @var \Illuminate\Contracts\Cache\Repository
*/
protected $cache;
/**
* @var \Illuminate\Contracts\Encryption\Encrypter
*/
protected $encrypter;
/**
* @var \PragmaRX\Google2FA\Google2FA
*/
protected $google2FA;
/**
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
*/
protected $repository;
/**
* Lockout time for failed login requests.
*
* @var int
*/
protected $lockoutTime;
/**
* After how many attempts should logins be throttled and locked.
*
* @var int
*/
protected $maxLoginAttempts;
/**
* Where to redirect users after login / registration.
*
* @var string
*/
protected $redirectTo = '/';
/**
* LoginController constructor.
*
* @param \Illuminate\Auth\AuthManager $auth
* @param \Illuminate\Contracts\Cache\Repository $cache
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
* @param \PragmaRX\Google2FA\Google2FA $google2FA
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository
*/
public function __construct(
AuthManager $auth,
CacheRepository $cache,
Encrypter $encrypter,
Google2FA $google2FA,
UserRepositoryInterface $repository
) {
$this->auth = $auth;
$this->cache = $cache;
$this->encrypter = $encrypter;
$this->google2FA = $google2FA;
$this->repository = $repository;
$this->lockoutTime = config('auth.lockout.time');
$this->maxLoginAttempts = config('auth.lockout.attempts');
}
/**
* Get the failed login response instance.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
*
* @throws \Pterodactyl\Exceptions\DisplayException
*/
protected function sendFailedLoginResponse(Request $request, Authenticatable $user = null)
{
$this->incrementLoginAttempts($request);
$this->fireFailedLoginEvent($user, [
$this->getField($request->input('user')) => $request->input('user'),
]);
if ($request->route()->named('auth.login-checkpoint')) {
throw new DisplayException(trans('auth.two_factor.checkpoint_failed'));
}
throw new DisplayException(trans('auth.failed'));
}
/**
* Send the response after the user was authenticated.
*
* @param \Pterodactyl\Models\User $user
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
protected function sendLoginResponse(User $user, Request $request): JsonResponse
{
$request->session()->regenerate();
$this->clearLoginAttempts($request);
$this->auth->guard()->login($user, true);
return response()->json([
'complete' => true,
'intended' => $this->redirectPath(),
'user' => $user->toVueObject(),
]);
}
/**
* Determine if the user is logging in using an email or username,.
*
* @param string $input
* @return string
*/
protected function getField(string $input = null): string
{
return str_contains($input, '@') ? 'email' : 'username';
}
/**
* Fire a failed login event.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param array $credentials
*/
protected function fireFailedLoginEvent(Authenticatable $user = null, array $credentials = [])
{
event(new Failed('auth', $user, $credentials));
}
}

View File

@ -3,7 +3,7 @@
namespace Pterodactyl\Http\Controllers\Auth;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Password;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Events\Auth\FailedPasswordReset;
@ -18,9 +18,9 @@ class ForgotPasswordController extends Controller
*
* @param \Illuminate\Http\Request
* @param string $response
* @return \Illuminate\Http\RedirectResponse
* @return \Illuminate\Http\JsonResponse
*/
protected function sendResetLinkFailedResponse(Request $request, $response): RedirectResponse
protected function sendResetLinkFailedResponse(Request $request, $response): JsonResponse
{
// As noted in #358 we will return success even if it failed
// to avoid pointing out that an account does or does not
@ -29,4 +29,17 @@ class ForgotPasswordController extends Controller
return $this->sendResetLinkResponse($request, Password::RESET_LINK_SENT);
}
/**
* Get the response for a successful password reset link.
*
* @param string $response
* @return \Illuminate\Http\JsonResponse
*/
protected function sendResetLinkResponse($response): JsonResponse
{
return response()->json([
'status' => trans($response),
]);
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Pterodactyl\Http\Controllers\Auth;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Http\Requests\Auth\LoginCheckpointRequest;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
class LoginCheckpointController extends AbstractLoginController
{
/**
* Handle a login where the user is required to provide a TOTP authentication
* token. Once a user has reached this stage it is assumed that they have already
* provided a valid username and password.
*
* @param \Pterodactyl\Http\Requests\Auth\LoginCheckpointRequest $request
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\DisplayException
*/
public function __invoke(LoginCheckpointRequest $request): JsonResponse
{
try {
$cache = $this->cache->pull($request->input('confirmation_token'), []);
$user = $this->repository->find(array_get($cache, 'user_id', 0));
} catch (RecordNotFoundException $exception) {
return $this->sendFailedLoginResponse($request);
}
if (array_get($cache, 'request_ip') !== $request->ip()) {
return $this->sendFailedLoginResponse($request, $user);
}
if (! $this->google2FA->verifyKey(
$this->encrypter->decrypt($user->totp_secret),
$request->input('authentication_code'),
config('pterodactyl.auth.2fa.window')
)) {
return $this->sendFailedLoginResponse($request, $user);
}
return $this->sendLoginResponse($user, $request);
}
}

View File

@ -3,116 +3,36 @@
namespace Pterodactyl\Http\Controllers\Auth;
use Illuminate\Http\Request;
use Illuminate\Auth\AuthManager;
use PragmaRX\Google2FA\Google2FA;
use Illuminate\Auth\Events\Failed;
use Illuminate\Http\RedirectResponse;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Encryption\Encrypter;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Contracts\Cache\Repository as CacheRepository;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Contracts\View\View;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
class LoginController extends Controller
class LoginController extends AbstractLoginController
{
use AuthenticatesUsers;
const USER_INPUT_FIELD = 'user';
/**
* @var \Illuminate\Auth\AuthManager
*/
private $auth;
/**
* @var \Illuminate\Contracts\Cache\Repository
*/
private $cache;
/**
* @var \Illuminate\Contracts\Config\Repository
*/
private $config;
/**
* @var \Illuminate\Contracts\Encryption\Encrypter
*/
private $encrypter;
/**
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
*/
private $repository;
/**
* @var \PragmaRX\Google2FA\Google2FA
*/
private $google2FA;
/**
* Where to redirect users after login / registration.
* Handle all incoming requests for the authentication routes and render the
* base authentication view component. Vuejs will take over at this point and
* turn the login area into a SPA.
*
* @var string
* @return \Illuminate\Contracts\View\View
*/
protected $redirectTo = '/';
/**
* Lockout time for failed login requests.
*
* @var int
*/
protected $lockoutTime;
/**
* After how many attempts should logins be throttled and locked.
*
* @var int
*/
protected $maxLoginAttempts;
/**
* LoginController constructor.
*
* @param \Illuminate\Auth\AuthManager $auth
* @param \Illuminate\Contracts\Cache\Repository $cache
* @param \Illuminate\Contracts\Config\Repository $config
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
* @param \PragmaRX\Google2FA\Google2FA $google2FA
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository
*/
public function __construct(
AuthManager $auth,
CacheRepository $cache,
ConfigRepository $config,
Encrypter $encrypter,
Google2FA $google2FA,
UserRepositoryInterface $repository
) {
$this->auth = $auth;
$this->cache = $cache;
$this->config = $config;
$this->encrypter = $encrypter;
$this->google2FA = $google2FA;
$this->repository = $repository;
$this->lockoutTime = $this->config->get('auth.lockout.time');
$this->maxLoginAttempts = $this->config->get('auth.lockout.attempts');
public function index(): View
{
return view('templates/auth.core');
}
/**
* Handle a login request to the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Illuminate\Validation\ValidationException
*/
public function login(Request $request)
public function login(Request $request): JsonResponse
{
$username = $request->input(self::USER_INPUT_FIELD);
$username = $request->input('user');
$useColumn = $this->getField($username);
if ($this->hasTooManyLoginAttempts($request)) {
@ -126,122 +46,29 @@ class LoginController extends Controller
return $this->sendFailedLoginResponse($request);
}
$validCredentials = password_verify($request->input('password'), $user->password);
// Ensure that the account is using a valid username and password before trying to
// continue. Previously this was handled in the 2FA checkpoint, however that has
// a flaw in which you can discover if an account exists simply by seeing if you
// can proceede to the next step in the login process.
if (! password_verify($request->input('password'), $user->password)) {
return $this->sendFailedLoginResponse($request, $user);
}
// If the user is using 2FA we do not actually log them in at this step, we return
// a one-time token to link the 2FA credentials to this account via the UI.
if ($user->use_totp) {
$token = str_random(64);
$this->cache->put($token, ['user_id' => $user->id, 'valid_credentials' => $validCredentials], 5);
$token = str_random(128);
$this->cache->put($token, [
'user_id' => $user->id,
'request_ip' => $request->ip(),
], 5);
return redirect()->route('auth.totp')->with('authentication_token', $token);
return response()->json([
'complete' => false,
'login_token' => $token,
]);
}
if ($validCredentials) {
$this->auth->guard()->login($user, true);
return $this->sendLoginResponse($request);
}
return $this->sendFailedLoginResponse($request, $user);
}
/**
* Handle a TOTP implementation page.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View
*/
public function totp(Request $request)
{
$token = $request->session()->get('authentication_token');
if (is_null($token) || $this->auth->guard()->user()) {
return redirect()->route('auth.login');
}
return view('auth.totp', ['verify_key' => $token]);
}
/**
* Handle a login where the user is required to provide a TOTP authentication
* token. In order to add additional layers of security, users are not
* informed of an incorrect password until this stage, forcing them to
* provide a token on each login attempt.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response
*/
public function loginUsingTotp(Request $request)
{
if (is_null($request->input('verify_token'))) {
return $this->sendFailedLoginResponse($request);
}
try {
$cache = $this->cache->pull($request->input('verify_token'), []);
$user = $this->repository->find(array_get($cache, 'user_id', 0));
} catch (RecordNotFoundException $exception) {
return $this->sendFailedLoginResponse($request);
}
if (is_null($request->input('2fa_token')) || ! array_get($cache, 'valid_credentials')) {
return $this->sendFailedLoginResponse($request, $user);
}
if (! $this->google2FA->verifyKey(
$this->encrypter->decrypt($user->totp_secret),
$request->input('2fa_token'),
$this->config->get('pterodactyl.auth.2fa.window')
)) {
return $this->sendFailedLoginResponse($request, $user);
}
$this->auth->guard()->login($user, true);
return $this->sendLoginResponse($request);
}
/**
* Get the failed login response instance.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @return \Illuminate\Http\RedirectResponse
*/
protected function sendFailedLoginResponse(Request $request, Authenticatable $user = null): RedirectResponse
{
$this->incrementLoginAttempts($request);
$this->fireFailedLoginEvent($user, [
$this->getField($request->input(self::USER_INPUT_FIELD)) => $request->input(self::USER_INPUT_FIELD),
]);
$errors = [self::USER_INPUT_FIELD => trans('auth.failed')];
if ($request->expectsJson()) {
return response()->json($errors, 422);
}
return redirect()->route('auth.login')
->withInput($request->only(self::USER_INPUT_FIELD))
->withErrors($errors);
}
/**
* Determine if the user is logging in using an email or username,.
*
* @param string $input
* @return string
*/
private function getField(string $input = null): string
{
return str_contains($input, '@') ? 'email' : 'username';
}
/**
* Fire a failed login event.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param array $credentials
*/
private function fireFailedLoginEvent(Authenticatable $user = null, array $credentials = [])
{
event(new Failed(config('auth.defaults.guard'), $user, $credentials));
return $this->sendLoginResponse($user, $request);
}
}

View File

@ -3,13 +3,15 @@
namespace Pterodactyl\Http\Controllers\Auth;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Prologue\Alerts\AlertsMessageBag;
use Illuminate\Http\JsonResponse;
use Illuminate\Contracts\Hashing\Hasher;
use Illuminate\Support\Facades\Password;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Contracts\Events\Dispatcher;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Pterodactyl\Http\Requests\Auth\ResetPasswordRequest;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
class ResetPasswordController extends Controller
@ -28,11 +30,6 @@ class ResetPasswordController extends Controller
*/
protected $hasTwoFactor = false;
/**
* @var \Prologue\Alerts\AlertsMessageBag
*/
private $alerts;
/**
* @var \Illuminate\Contracts\Events\Dispatcher
*/
@ -51,31 +48,44 @@ class ResetPasswordController extends Controller
/**
* ResetPasswordController constructor.
*
* @param \Prologue\Alerts\AlertsMessageBag $alerts
* @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
* @param \Illuminate\Contracts\Hashing\Hasher $hasher
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository
*/
public function __construct(AlertsMessageBag $alerts, Dispatcher $dispatcher, Hasher $hasher, UserRepositoryInterface $userRepository)
public function __construct(Dispatcher $dispatcher, Hasher $hasher, UserRepositoryInterface $userRepository)
{
$this->alerts = $alerts;
$this->dispatcher = $dispatcher;
$this->hasher = $hasher;
$this->userRepository = $userRepository;
}
/**
* Return the rules used when validating password reset.
* Reset the given user's password.
*
* @return array
* @param \Pterodactyl\Http\Requests\Auth\ResetPasswordRequest $request
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\DisplayException
*/
protected function rules(): array
public function __invoke(ResetPasswordRequest $request): JsonResponse
{
return [
'token' => 'required',
'email' => 'required|email',
'password' => 'required|confirmed|min:8',
];
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$response = $this->broker()->reset(
$this->credentials($request), function ($user, $password) {
$this->resetPassword($user, $password);
}
);
// If the password was successfully reset, we will redirect the user back to
// the application's home authenticated view. If there is an error we can
// redirect them back to where they came from with their error message.
if ($response === Password::PASSWORD_RESET) {
return $this->sendResetResponse();
}
throw new DisplayException(trans($response));
}
/**
@ -108,19 +118,16 @@ class ResetPasswordController extends Controller
}
/**
* Get the response for a successful password reset.
* Send a successful password reset response back to the callee.
*
* @param \Illuminate\Http\Request $request
* @param string $response
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
protected function sendResetResponse(Request $request, $response)
protected function sendResetResponse(): JsonResponse
{
if ($this->hasTwoFactor) {
$this->alerts->success('Your password was successfully updated. Please log in to continue.')->flash();
}
return redirect($this->hasTwoFactor ? route('auth.login') : $this->redirectPath())
->with('status', trans($response));
return response()->json([
'success' => true,
'redirect_to' => $this->redirectTo,
'send_to_login' => $this->hasTwoFactor,
]);
}
}

View File

@ -1,91 +0,0 @@
<?php
namespace Pterodactyl\Http\Controllers\Base;
use Pterodactyl\Models\User;
use Illuminate\Auth\AuthManager;
use Prologue\Alerts\AlertsMessageBag;
use Illuminate\Contracts\Session\Session;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Users\UserUpdateService;
use Pterodactyl\Traits\Helpers\AvailableLanguages;
use Pterodactyl\Http\Requests\Base\AccountDataFormRequest;
class AccountController extends Controller
{
use AvailableLanguages;
/**
* @var \Prologue\Alerts\AlertsMessageBag
*/
protected $alert;
/**
* @var \Illuminate\Auth\SessionGuard
*/
protected $sessionGuard;
/**
* @var \Pterodactyl\Services\Users\UserUpdateService
*/
protected $updateService;
/**
* AccountController constructor.
*
* @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Illuminate\Auth\AuthManager $authManager
* @param \Pterodactyl\Services\Users\UserUpdateService $updateService
*/
public function __construct(AlertsMessageBag $alert, AuthManager $authManager, UserUpdateService $updateService)
{
$this->alert = $alert;
$this->updateService = $updateService;
$this->sessionGuard = $authManager->guard();
}
/**
* Display base account information page.
*
* @return \Illuminate\View\View
*/
public function index()
{
return view('base.account', [
'languages' => $this->getAvailableLanguages(true),
]);
}
/**
* Update details for a user's account.
*
* @param \Pterodactyl\Http\Requests\Base\AccountDataFormRequest $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function update(AccountDataFormRequest $request)
{
// Prevent logging this specific session out when the password is changed. This will
// automatically update the user's password anyways, so no need to do anything else here.
if ($request->input('do_action') === 'password') {
$this->sessionGuard->logoutOtherDevices($request->input('new_password'));
} else {
if ($request->input('do_action') === 'email') {
$data = ['email' => $request->input('new_email')];
} elseif ($request->input('do_action') === 'identity') {
$data = $request->only(['name_first', 'name_last', 'username', 'language']);
} else {
$data = [];
}
$this->updateService->setUserLevel(User::USER_LEVEL_USER);
$this->updateService->handle($request->user(), $data);
}
$this->alert->success(trans('base.account.details_updated'))->flash();
return redirect()->route('account');
}
}

View File

@ -53,13 +53,13 @@ class IndexController extends Controller
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function getIndex(Request $request)
public function index(Request $request)
{
$servers = $this->repository->setSearchTerm($request->input('query'))->filterUserAccessServers(
$request->user(), User::FILTER_LEVEL_ALL, config('pterodactyl.paginate.frontend.servers')
);
return view('base.index', ['servers' => $servers]);
return view('templates/base.core', ['servers' => $servers]);
}
/**

View File

@ -3,6 +3,7 @@
namespace Pterodactyl\Http\Controllers\Base;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Users\TwoFactorSetupService;
@ -62,36 +63,28 @@ class SecurityController extends Controller
}
/**
* Returns Security Management Page.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function index(Request $request)
{
if ($this->config->get('session.driver') === 'database') {
$activeSessions = $this->repository->getUserSessions($request->user()->id);
}
return view('base.security', [
'sessions' => $activeSessions ?? null,
]);
}
/**
* Generates TOTP Secret and returns popup data for user to verify
* that they can generate a valid response.
* Return information about the user's two-factor authentication status. If not enabled setup their
* secret and return information to allow the user to proceede with setup.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function generateTotp(Request $request)
public function index(Request $request): JsonResponse
{
return response()->json([
'qrImage' => $this->twoFactorSetupService->handle($request->user()),
if ($request->user()->use_totp) {
return JsonResponse::create([
'enabled' => true,
]);
}
$response = $this->twoFactorSetupService->handle($request->user());
return JsonResponse::create([
'enabled' => false,
'qr_image' => $response->get('image'),
'secret' => $response->get('secret'),
]);
}
@ -99,53 +92,43 @@ class SecurityController extends Controller
* Verifies that 2FA token received is valid and will work on the account.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function setTotp(Request $request)
public function store(Request $request): JsonResponse
{
try {
$this->toggleTwoFactorService->handle($request->user(), $request->input('token') ?? '');
return response('true');
} catch (TwoFactorAuthenticationTokenInvalid $exception) {
return response('false');
$error = true;
}
return JsonResponse::create([
'success' => ! isset($error),
]);
}
/**
* Disables TOTP on an account.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function disableTotp(Request $request)
public function delete(Request $request): JsonResponse
{
try {
$this->toggleTwoFactorService->handle($request->user(), $request->input('token') ?? '', false);
} catch (TwoFactorAuthenticationTokenInvalid $exception) {
$this->alert->danger(trans('base.security.2fa_disable_error'))->flash();
$error = true;
}
return redirect()->route('account.security');
}
/**
* Revokes a user session.
*
* @param \Illuminate\Http\Request $request
* @param string $id
* @return \Illuminate\Http\RedirectResponse
*/
public function revoke(Request $request, string $id)
{
$this->repository->deleteUserSession($request->user()->id, $id);
return redirect()->route('account.security');
return JsonResponse::create([
'success' => ! isset($error),
]);
}
}

View File

@ -1,72 +0,0 @@
<?php
namespace Pterodactyl\Http\Controllers\Server;
use Illuminate\View\View;
use Illuminate\Http\Request;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Traits\Controllers\JavascriptInjection;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
class ConsoleController extends Controller
{
use JavascriptInjection;
/**
* @var \Illuminate\Contracts\Config\Repository
*/
protected $config;
/**
* ConsoleController constructor.
*
* @param \Illuminate\Contracts\Config\Repository $config
*/
public function __construct(ConfigRepository $config)
{
$this->config = $config;
}
/**
* Render server index page with the console and power options.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function index(Request $request): View
{
$server = $request->attributes->get('server');
$this->setRequest($request)->injectJavascript([
'server' => [
'cpu' => $server->cpu,
],
'meta' => [
'saveFile' => route('server.files.save', $server->uuidShort),
'csrfToken' => csrf_token(),
],
'config' => [
'console_count' => $this->config->get('pterodactyl.console.count'),
'console_freq' => $this->config->get('pterodactyl.console.frequency'),
],
]);
return view('server.index');
}
/**
* Render a stand-alone console in the browser.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function console(Request $request): View
{
$this->setRequest($request)->injectJavascript(['config' => [
'console_count' => $this->config->get('pterodactyl.console.count'),
'console_freq' => $this->config->get('pterodactyl.console.frequency'),
]]);
return view('server.console');
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Pterodactyl\Http\Controllers\Server;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService;
class CredentialsController extends Controller
{
/**
* @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService
*/
private $keyProviderService;
/**
* CredentialsController constructor.
*
* @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService
*/
public function __construct(DaemonKeyProviderService $keyProviderService)
{
$this->keyProviderService = $keyProviderService;
}
/**
* Return a set of credentials that the currently authenticated user can use to access
* a given server with.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function index(Request $request): JsonResponse
{
/** @var \Pterodactyl\Models\Server $server */
$server = $request->attributes->get('server');
$server->loadMissing('node');
return JsonResponse::create([
'node' => $server->getRelation('node')->getConnectionAddress(),
'key' => $this->keyProviderService->handle($server, $request->user()),
]);
}
}

View File

@ -1,165 +0,0 @@
<?php
namespace Pterodactyl\Http\Controllers\Server;
use Illuminate\View\View;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Traits\Controllers\JavascriptInjection;
use Pterodactyl\Services\Databases\DatabasePasswordService;
use Pterodactyl\Services\Databases\DatabaseManagementService;
use Pterodactyl\Services\Databases\DeployServerDatabaseService;
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface;
use Pterodactyl\Http\Requests\Server\Database\StoreServerDatabaseRequest;
use Pterodactyl\Http\Requests\Server\Database\DeleteServerDatabaseRequest;
class DatabaseController extends Controller
{
use JavascriptInjection;
/**
* @var \Prologue\Alerts\AlertsMessageBag
*/
private $alert;
/**
* @var \Pterodactyl\Services\Databases\DeployServerDatabaseService
*/
private $deployServerDatabaseService;
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface
*/
private $databaseHostRepository;
/**
* @var \Pterodactyl\Services\Databases\DatabaseManagementService
*/
private $managementService;
/**
* @var \Pterodactyl\Services\Databases\DatabasePasswordService
*/
private $passwordService;
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface
*/
private $repository;
/**
* DatabaseController constructor.
*
* @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Pterodactyl\Services\Databases\DeployServerDatabaseService $deployServerDatabaseService
* @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $databaseHostRepository
* @param \Pterodactyl\Services\Databases\DatabaseManagementService $managementService
* @param \Pterodactyl\Services\Databases\DatabasePasswordService $passwordService
* @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository
*/
public function __construct(
AlertsMessageBag $alert,
DeployServerDatabaseService $deployServerDatabaseService,
DatabaseHostRepositoryInterface $databaseHostRepository,
DatabaseManagementService $managementService,
DatabasePasswordService $passwordService,
DatabaseRepositoryInterface $repository
) {
$this->alert = $alert;
$this->databaseHostRepository = $databaseHostRepository;
$this->deployServerDatabaseService = $deployServerDatabaseService;
$this->managementService = $managementService;
$this->passwordService = $passwordService;
$this->repository = $repository;
}
/**
* Render the database listing for a server.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index(Request $request): View
{
$server = $request->attributes->get('server');
$this->authorize('view-databases', $server);
$this->setRequest($request)->injectJavascript();
$canCreateDatabase = config('pterodactyl.client_features.databases.enabled');
$allowRandom = config('pterodactyl.client_features.databases.allow_random');
if ($this->databaseHostRepository->findCountWhere([['node_id', '=', $server->node_id]]) === 0) {
if ($canCreateDatabase && ! $allowRandom) {
$canCreateDatabase = false;
}
}
$databases = $this->repository->getDatabasesForServer($server->id);
return view('server.databases.index', [
'allowCreation' => $canCreateDatabase,
'overLimit' => ! is_null($server->database_limit) && count($databases) >= $server->database_limit,
'databases' => $databases,
]);
}
/**
* Handle a request from a user to create a new database for the server.
*
* @param \Pterodactyl\Http\Requests\Server\Database\StoreServerDatabaseRequest $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Exception
* @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException
*/
public function store(StoreServerDatabaseRequest $request): RedirectResponse
{
$this->deployServerDatabaseService->handle($request->getServer(), $request->validated());
$this->alert->success('Successfully created a new database.')->flash();
return redirect()->route('server.databases.index', $request->getServer()->uuidShort);
}
/**
* Handle a request to update the password for a specific database.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*
* @throws \Illuminate\Auth\Access\AuthorizationException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function update(Request $request): JsonResponse
{
$this->authorize('reset-db-password', $request->attributes->get('server'));
$password = str_random(24);
$this->passwordService->handle($request->attributes->get('database'), $password);
return response()->json(['password' => $password]);
}
/**
* Delete a database for this server from the SQL server and Panel database.
*
* @param \Pterodactyl\Http\Requests\Server\Database\DeleteServerDatabaseRequest $request
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function delete(DeleteServerDatabaseRequest $request): Response
{
$this->managementService->delete($request->attributes->get('database')->id);
return response('', Response::HTTP_NO_CONTENT);
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace Pterodactyl\Http\Controllers\Server;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use GuzzleHttp\Exception\TransferException;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
class FileController extends Controller
{
/**
* @var \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface
*/
private $fileRepository;
/**
* FileController constructor.
*
* @param \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface $fileRepository
*/
public function __construct(FileRepositoryInterface $fileRepository)
{
$this->fileRepository = $fileRepository;
}
/**
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*
* @throws \Illuminate\Auth\Access\AuthorizationException
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function index(Request $request): JsonResponse
{
$server = $request->attributes->get('server');
$this->authorize('list-files', $server);
$requestDirectory = '/' . trim(urldecode($request->route()->parameter('directory', '/')), '/');
try {
$contents = $this->fileRepository->setServer($server)->setToken(
$request->attributes->get('server_token')
)->getDirectory($requestDirectory);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception, true);
}
return JsonResponse::create([
'contents' => $contents,
'editable' => config('pterodactyl.files.editable'),
'current_directory' => $requestDirectory,
]);
}
}

View File

@ -1,57 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Http\Controllers\Server\Files;
use Ramsey\Uuid\Uuid;
use Illuminate\Http\Request;
use Illuminate\Cache\Repository;
use Illuminate\Http\RedirectResponse;
use Pterodactyl\Http\Controllers\Controller;
class DownloadController extends Controller
{
/**
* @var \Illuminate\Cache\Repository
*/
protected $cache;
/**
* DownloadController constructor.
*
* @param \Illuminate\Cache\Repository $cache
*/
public function __construct(Repository $cache)
{
$this->cache = $cache;
}
/**
* Setup a unique download link for a user to download a file from.
*
* @param \Illuminate\Http\Request $request
* @param string $uuid
* @param string $file
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index(Request $request, string $uuid, string $file): RedirectResponse
{
$server = $request->attributes->get('server');
$this->authorize('download-files', $server);
$token = Uuid::uuid4()->toString();
$node = $server->getRelation('node');
$this->cache->put('Server:Downloads:' . $token, ['server' => $server->uuid, 'path' => $file], 5);
return redirect(sprintf('%s://%s:%s/v1/server/file/download/%s', $node->scheme, $node->fqdn, $node->daemonListen, $token));
}
}

View File

@ -1,120 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Http\Controllers\Server\Files;
use Illuminate\View\View;
use Illuminate\Http\Request;
use GuzzleHttp\Exception\RequestException;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Traits\Controllers\JavascriptInjection;
use Pterodactyl\Http\Requests\Server\UpdateFileContentsFormRequest;
use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
class FileActionsController extends Controller
{
use JavascriptInjection;
/**
* @var \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface
*/
protected $repository;
/**
* FileActionsController constructor.
*
* @param \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface $repository
*/
public function __construct(FileRepositoryInterface $repository)
{
$this->repository = $repository;
}
/**
* Display server file index list.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index(Request $request): View
{
$server = $request->attributes->get('server');
$this->authorize('list-files', $server);
$this->setRequest($request)->injectJavascript([
'meta' => [
'directoryList' => route('server.files.directory-list', $server->uuidShort),
'csrftoken' => csrf_token(),
],
'permissions' => [
'moveFiles' => $request->user()->can('move-files', $server),
'copyFiles' => $request->user()->can('copy-files', $server),
'compressFiles' => $request->user()->can('compress-files', $server),
'decompressFiles' => $request->user()->can('decompress-files', $server),
'createFiles' => $request->user()->can('create-files', $server),
'downloadFiles' => $request->user()->can('download-files', $server),
'deleteFiles' => $request->user()->can('delete-files', $server),
],
]);
return view('server.files.index');
}
/**
* Render page to manually create a file in the panel.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create(Request $request): View
{
$this->authorize('create-files', $request->attributes->get('server'));
$this->setRequest($request)->injectJavascript();
return view('server.files.add', [
'directory' => (in_array($request->get('dir'), [null, '/', ''])) ? '' : trim($request->get('dir'), '/') . '/',
]);
}
/**
* Display a form to allow for editing of a file.
*
* @param \Pterodactyl\Http\Requests\Server\UpdateFileContentsFormRequest $request
* @param string $uuid
* @param string $file
* @return \Illuminate\View\View
*
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function view(UpdateFileContentsFormRequest $request, string $uuid, string $file): View
{
$server = $request->attributes->get('server');
$dirname = str_replace('\\', '/', pathinfo($file, PATHINFO_DIRNAME));
try {
$content = $this->repository->setServer($server)->setToken($request->attributes->get('server_token'))->getContent($file);
} catch (RequestException $exception) {
throw new DaemonConnectionException($exception);
}
$this->setRequest($request)->injectJavascript(['stat' => $request->attributes->get('file_stats')]);
return view('server.files.edit', [
'file' => $file,
'stat' => $request->attributes->get('file_stats'),
'contents' => $content,
'directory' => (in_array($dirname, ['.', './', '/'])) ? '/' : trim($dirname, '/') . '/',
]);
}
}

View File

@ -1,105 +0,0 @@
<?php
namespace Pterodactyl\Http\Controllers\Server\Files;
use Illuminate\View\View;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use GuzzleHttp\Exception\RequestException;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
class RemoteRequestController extends Controller
{
/**
* @var \Illuminate\Contracts\Config\Repository
*/
protected $config;
/**
* @var \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface
*/
protected $repository;
/**
* RemoteRequestController constructor.
*
* @param \Illuminate\Contracts\Config\Repository $config
* @param \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface $repository
*/
public function __construct(ConfigRepository $config, FileRepositoryInterface $repository)
{
$this->config = $config;
$this->repository = $repository;
}
/**
* Return a listing of a servers file directory.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*
* @throws \Illuminate\Auth\Access\AuthorizationException
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function directory(Request $request): View
{
$server = $request->attributes->get('server');
$this->authorize('list-files', $server);
$requestDirectory = '/' . trim(urldecode($request->input('directory', '/')), '/');
$directory = [
'header' => $requestDirectory !== '/' ? $requestDirectory : '',
'first' => $requestDirectory !== '/',
];
$goBack = explode('/', trim($requestDirectory, '/'));
if (! empty(array_filter($goBack)) && count($goBack) >= 2) {
array_pop($goBack);
$directory['show'] = true;
$directory['link'] = '/' . implode('/', $goBack);
$directory['link_show'] = implode('/', $goBack) . '/';
}
try {
$listing = $this->repository->setServer($server)->setToken($request->attributes->get('server_token'))->getDirectory($requestDirectory);
} catch (RequestException $exception) {
throw new DaemonConnectionException($exception, true);
}
return view('server.files.list', [
'files' => $listing['files'],
'folders' => $listing['folders'],
'editableMime' => $this->config->get('pterodactyl.files.editable'),
'directory' => $directory,
]);
}
/**
* Put the contents of a file onto the daemon.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*
* @throws \Illuminate\Auth\Access\AuthorizationException
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function store(Request $request): Response
{
$server = $request->attributes->get('server');
$this->authorize('save-files', $server);
try {
$this->repository->setServer($server)->setToken($request->attributes->get('server_token'))
->putContent($request->input('file'), $request->input('contents') ?? '');
return response('', 204);
} catch (RequestException $exception) {
throw new DaemonConnectionException($exception);
}
}
}

View File

@ -1,96 +0,0 @@
<?php
namespace Pterodactyl\Http\Controllers\Server\Settings;
use Illuminate\View\View;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Contracts\Extensions\HashidsInterface;
use Pterodactyl\Traits\Controllers\JavascriptInjection;
use Pterodactyl\Services\Allocations\SetDefaultAllocationService;
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
use Pterodactyl\Exceptions\Service\Allocation\AllocationDoesNotBelongToServerException;
class AllocationController extends Controller
{
use JavascriptInjection;
/**
* @var \Pterodactyl\Services\Allocations\SetDefaultAllocationService
*/
private $defaultAllocationService;
/**
* @var \Pterodactyl\Contracts\Extensions\HashidsInterface
*/
private $hashids;
/**
* @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface
*/
private $repository;
/**
* AllocationController constructor.
*
* @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $repository
* @param \Pterodactyl\Contracts\Extensions\HashidsInterface $hashids
* @param \Pterodactyl\Services\Allocations\SetDefaultAllocationService $defaultAllocationService
*/
public function __construct(
AllocationRepositoryInterface $repository,
HashidsInterface $hashids,
SetDefaultAllocationService $defaultAllocationService
) {
$this->defaultAllocationService = $defaultAllocationService;
$this->hashids = $hashids;
$this->repository = $repository;
}
/**
* Render the allocation management overview page for a server.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index(Request $request): View
{
$server = $request->attributes->get('server');
$this->authorize('view-allocations', $server);
$this->setRequest($request)->injectJavascript();
return view('server.settings.allocation', [
'allocations' => $this->repository->findWhere([['server_id', '=', $server->id]]),
]);
}
/**
* Update the default allocation for a server.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*
* @throws \Illuminate\Auth\Access\AuthorizationException
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function update(Request $request): JsonResponse
{
$server = $request->attributes->get('server');
$this->authorize('edit-allocation', $server);
$allocation = $this->hashids->decodeFirst($request->input('allocation'), 0);
try {
$this->defaultAllocationService->handle($server->id, $allocation);
} catch (AllocationDoesNotBelongToServerException $exception) {
return response()->json(['error' => 'No matching allocation was located for this server.'], 404);
}
return response()->json();
}
}

View File

@ -1,59 +0,0 @@
<?php
namespace Pterodactyl\Http\Controllers\Server\Settings;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Traits\Controllers\JavascriptInjection;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Http\Requests\Server\Settings\ChangeServerNameRequest;
class NameController extends Controller
{
use JavascriptInjection;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
private $repository;
/**
* NameController constructor.
*
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
*/
public function __construct(ServerRepositoryInterface $repository)
{
$this->repository = $repository;
}
/**
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index(Request $request)
{
$this->authorize('view-name', $request->attributes->get('server'));
$this->setRequest($request)->injectJavascript();
return view('server.settings.name');
}
/**
* Update the stored name for a specific server.
*
* @param \Pterodactyl\Http\Requests\Server\Settings\ChangeServerNameRequest $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function update(ChangeServerNameRequest $request): RedirectResponse
{
$this->repository->update($request->getServer()->id, $request->validated());
return redirect()->route('server.settings.name', $request->getServer()->uuidShort);
}
}

View File

@ -1,29 +0,0 @@
<?php
namespace Pterodactyl\Http\Controllers\Server\Settings;
use Illuminate\View\View;
use Illuminate\Http\Request;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Traits\Controllers\JavascriptInjection;
class SftpController extends Controller
{
use JavascriptInjection;
/**
* Render the server SFTP settings page.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index(Request $request): View
{
$this->authorize('access-sftp', $request->attributes->get('server'));
$this->setRequest($request)->injectJavascript();
return view('server.settings.sftp');
}
}

View File

@ -1,96 +0,0 @@
<?php
namespace Pterodactyl\Http\Controllers\Server\Settings;
use Illuminate\View\View;
use Illuminate\Http\Request;
use Pterodactyl\Models\User;
use Illuminate\Http\RedirectResponse;
use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Traits\Controllers\JavascriptInjection;
use Pterodactyl\Services\Servers\StartupCommandViewService;
use Pterodactyl\Services\Servers\StartupModificationService;
use Pterodactyl\Http\Requests\Server\UpdateStartupParametersFormRequest;
class StartupController extends Controller
{
use JavascriptInjection;
/**
* @var \Prologue\Alerts\AlertsMessageBag
*/
private $alert;
/**
* @var \Pterodactyl\Services\Servers\StartupCommandViewService
*/
private $commandViewService;
/**
* @var \Pterodactyl\Services\Servers\StartupModificationService
*/
private $modificationService;
/**
* StartupController constructor.
*
* @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Pterodactyl\Services\Servers\StartupCommandViewService $commandViewService
* @param \Pterodactyl\Services\Servers\StartupModificationService $modificationService
*/
public function __construct(
AlertsMessageBag $alert,
StartupCommandViewService $commandViewService,
StartupModificationService $modificationService
) {
$this->alert = $alert;
$this->commandViewService = $commandViewService;
$this->modificationService = $modificationService;
}
/**
* Render the server startup page.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*
* @throws \Illuminate\Auth\Access\AuthorizationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function index(Request $request): View
{
$server = $request->attributes->get('server');
$this->authorize('view-startup', $server);
$this->setRequest($request)->injectJavascript();
$data = $this->commandViewService->handle($server->id);
return view('server.settings.startup', [
'variables' => $data->get('variables'),
'server_values' => $data->get('server_values'),
'startup' => $data->get('startup'),
]);
}
/**
* Handle request to update the startup variables for a server. Authorization
* is handled in the form request.
*
* @param \Pterodactyl\Http\Requests\Server\UpdateStartupParametersFormRequest $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Illuminate\Validation\ValidationException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function update(UpdateStartupParametersFormRequest $request): RedirectResponse
{
$this->modificationService->setUserLevel(User::USER_LEVEL_USER);
$this->modificationService->handle($request->attributes->get('server'), $request->normalize());
$this->alert->success(trans('server.config.startup.edited'))->flash();
return redirect()->route('server.settings.startup', ['server' => $request->attributes->get('server')->uuidShort]);
}
}

View File

@ -1,197 +0,0 @@
<?php
namespace Pterodactyl\Http\Controllers\Server;
use Illuminate\View\View;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Pterodactyl\Models\Permission;
use Illuminate\Http\RedirectResponse;
use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Subusers\SubuserUpdateService;
use Pterodactyl\Traits\Controllers\JavascriptInjection;
use Pterodactyl\Services\Subusers\SubuserCreationService;
use Pterodactyl\Services\Subusers\SubuserDeletionService;
use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
use Pterodactyl\Http\Requests\Server\Subuser\SubuserStoreFormRequest;
use Pterodactyl\Http\Requests\Server\Subuser\SubuserUpdateFormRequest;
class SubuserController extends Controller
{
use JavascriptInjection;
/**
* @var \Prologue\Alerts\AlertsMessageBag
*/
protected $alert;
/**
* @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface
*/
protected $repository;
/**
* @var \Pterodactyl\Services\Subusers\SubuserCreationService
*/
protected $subuserCreationService;
/**
* @var \Pterodactyl\Services\Subusers\SubuserDeletionService
*/
protected $subuserDeletionService;
/**
* @var \Pterodactyl\Services\Subusers\SubuserUpdateService
*/
protected $subuserUpdateService;
/**
* SubuserController constructor.
*
* @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Pterodactyl\Services\Subusers\SubuserCreationService $subuserCreationService
* @param \Pterodactyl\Services\Subusers\SubuserDeletionService $subuserDeletionService
* @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository
* @param \Pterodactyl\Services\Subusers\SubuserUpdateService $subuserUpdateService
*/
public function __construct(
AlertsMessageBag $alert,
SubuserCreationService $subuserCreationService,
SubuserDeletionService $subuserDeletionService,
SubuserRepositoryInterface $repository,
SubuserUpdateService $subuserUpdateService
) {
$this->alert = $alert;
$this->repository = $repository;
$this->subuserCreationService = $subuserCreationService;
$this->subuserDeletionService = $subuserDeletionService;
$this->subuserUpdateService = $subuserUpdateService;
}
/**
* Displays the subuser overview index.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index(Request $request): View
{
$server = $request->attributes->get('server');
$this->authorize('list-subusers', $server);
$this->setRequest($request)->injectJavascript();
return view('server.users.index', [
'subusers' => $this->repository->findWhere([['server_id', '=', $server->id]]),
]);
}
/**
* Displays a single subuser overview.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function view(Request $request): View
{
$server = $request->attributes->get('server');
$this->authorize('view-subuser', $server);
$subuser = $this->repository->getWithPermissions($request->attributes->get('subuser'));
$this->setRequest($request)->injectJavascript();
return view('server.users.view', [
'subuser' => $subuser,
'permlist' => Permission::getPermissions(),
'permissions' => $subuser->getRelation('permissions')->mapWithKeys(function ($item) {
return [$item->permission => true];
}),
]);
}
/**
* Handles editing a subuser.
*
* @param \Pterodactyl\Http\Requests\Server\Subuser\SubuserUpdateFormRequest $request
* @param string $uuid
* @param string $hash
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Illuminate\Auth\Access\AuthorizationException
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function update(SubuserUpdateFormRequest $request, string $uuid, string $hash): RedirectResponse
{
$this->subuserUpdateService->handle($request->attributes->get('subuser'), $request->input('permissions', []));
$this->alert->success(trans('server.users.user_updated'))->flash();
return redirect()->route('server.subusers.view', ['uuid' => $uuid, 'subuser' => $hash]);
}
/**
* Display new subuser creation page.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create(Request $request): View
{
$server = $request->attributes->get('server');
$this->authorize('create-subuser', $server);
$this->setRequest($request)->injectJavascript();
return view('server.users.new', ['permissions' => Permission::getPermissions()]);
}
/**
* Handles creating a new subuser.
*
* @param \Pterodactyl\Http\Requests\Server\Subuser\SubuserStoreFormRequest $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Exception
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException
* @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException
*/
public function store(SubuserStoreFormRequest $request): RedirectResponse
{
$server = $request->attributes->get('server');
$subuser = $this->subuserCreationService->handle($server, $request->input('email'), $request->input('permissions', []));
$this->alert->success(trans('server.users.user_assigned'))->flash();
return redirect()->route('server.subusers.view', [
'uuid' => $server->uuidShort,
'id' => $subuser->hashid,
]);
}
/**
* Handles deleting a subuser.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*
* @throws \Illuminate\Auth\Access\AuthorizationException
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function delete(Request $request): Response
{
$server = $request->attributes->get('server');
$this->authorize('delete-subuser', $server);
$this->subuserDeletionService->handle($request->attributes->get('subuser'));
return response('', 204);
}
}

View File

@ -1,79 +0,0 @@
<?php
namespace Pterodactyl\Http\Controllers\Server\Tasks;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Schedules\ProcessScheduleService;
use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface;
class ActionController extends Controller
{
/**
* @var \Pterodactyl\Services\Schedules\ProcessScheduleService
*/
private $processScheduleService;
/**
* @var \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface
*/
private $repository;
/**
* ActionController constructor.
*
* @param \Pterodactyl\Services\Schedules\ProcessScheduleService $processScheduleService
* @param \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface $repository
*/
public function __construct(ProcessScheduleService $processScheduleService, ScheduleRepositoryInterface $repository)
{
$this->processScheduleService = $processScheduleService;
$this->repository = $repository;
}
/**
* Toggle a task to be active or inactive for a given server.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*
* @throws \Illuminate\Auth\Access\AuthorizationException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function toggle(Request $request): Response
{
$server = $request->attributes->get('server');
$schedule = $request->attributes->get('schedule');
$this->authorize('toggle-schedule', $server);
$this->repository->update($schedule->id, [
'is_active' => ! $schedule->is_active,
]);
return response('', 204);
}
/**
* Trigger a schedule to run now.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*
* @throws \Illuminate\Auth\Access\AuthorizationException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function trigger(Request $request): Response
{
$server = $request->attributes->get('server');
$this->authorize('toggle-schedule', $server);
$this->processScheduleService->handle(
$request->attributes->get('schedule')
);
return response('', 204);
}
}

View File

@ -1,198 +0,0 @@
<?php
namespace Pterodactyl\Http\Controllers\Server\Tasks;
use Illuminate\View\View;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Http\RedirectResponse;
use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Contracts\Extensions\HashidsInterface;
use Pterodactyl\Traits\Controllers\JavascriptInjection;
use Pterodactyl\Services\Schedules\ScheduleUpdateService;
use Pterodactyl\Services\Schedules\ScheduleCreationService;
use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface;
use Pterodactyl\Http\Requests\Server\ScheduleCreationFormRequest;
class TaskManagementController extends Controller
{
use JavascriptInjection;
/**
* @var \Prologue\Alerts\AlertsMessageBag
*/
protected $alert;
/**
* @var \Pterodactyl\Services\Schedules\ScheduleCreationService
*/
protected $creationService;
/**
* @var \Pterodactyl\Contracts\Extensions\HashidsInterface
*/
protected $hashids;
/**
* @var \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface
*/
protected $repository;
/**
* @var \Pterodactyl\Services\Schedules\ScheduleUpdateService
*/
private $updateService;
/**
* TaskManagementController constructor.
*
* @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Pterodactyl\Contracts\Extensions\HashidsInterface $hashids
* @param \Pterodactyl\Services\Schedules\ScheduleCreationService $creationService
* @param \Pterodactyl\Services\Schedules\ScheduleUpdateService $updateService
* @param \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface $repository
*/
public function __construct(
AlertsMessageBag $alert,
HashidsInterface $hashids,
ScheduleCreationService $creationService,
ScheduleUpdateService $updateService,
ScheduleRepositoryInterface $repository
) {
$this->alert = $alert;
$this->creationService = $creationService;
$this->hashids = $hashids;
$this->repository = $repository;
$this->updateService = $updateService;
}
/**
* Display the task page listing.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index(Request $request): View
{
$server = $request->attributes->get('server');
$this->authorize('list-schedules', $server);
$this->setRequest($request)->injectJavascript();
return view('server.schedules.index', [
'schedules' => $this->repository->findServerSchedules($server->id),
'actions' => [
'command' => trans('server.schedule.actions.command'),
'power' => trans('server.schedule.actions.power'),
],
]);
}
/**
* Display the task creation page.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create(Request $request): View
{
$server = $request->attributes->get('server');
$this->authorize('create-schedule', $server);
$this->setRequest($request)->injectJavascript();
return view('server.schedules.new');
}
/**
* Handle request to store a new schedule and tasks in the database.
*
* @param \Pterodactyl\Http\Requests\Server\ScheduleCreationFormRequest $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Service\Schedule\Task\TaskIntervalTooLongException
*/
public function store(ScheduleCreationFormRequest $request): RedirectResponse
{
$server = $request->attributes->get('server');
$schedule = $this->creationService->handle($server, $request->normalize(), $request->getTasks());
$this->alert->success(trans('server.schedule.schedule_created'))->flash();
return redirect()->route('server.schedules.view', [
'server' => $server->uuidShort,
'schedule' => $schedule->hashid,
]);
}
/**
* Return a view to modify a schedule.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function view(Request $request): View
{
$server = $request->attributes->get('server');
$schedule = $request->attributes->get('schedule');
$this->authorize('view-schedule', $server);
$this->setRequest($request)->injectJavascript([
'tasks' => $schedule->getRelation('tasks')->map(function ($task) {
/* @var \Pterodactyl\Models\Task $task */
return collect($task->toArray())->only('action', 'time_offset', 'payload')->all();
}),
]);
return view('server.schedules.view', ['schedule' => $schedule]);
}
/**
* Update a specific parent task on the system.
*
* @param \Pterodactyl\Http\Requests\Server\ScheduleCreationFormRequest $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\Schedule\Task\TaskIntervalTooLongException
*/
public function update(ScheduleCreationFormRequest $request): RedirectResponse
{
$server = $request->attributes->get('server');
$schedule = $request->attributes->get('schedule');
$this->updateService->handle($schedule, $request->normalize(), $request->getTasks());
$this->alert->success(trans('server.schedule.schedule_updated'))->flash();
return redirect()->route('server.schedules.view', [
'server' => $server->uuidShort,
'schedule' => $schedule->hashid,
]);
}
/**
* Delete a parent task from the Panel.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function delete(Request $request): Response
{
$server = $request->attributes->get('server');
$schedule = $request->attributes->get('schedule');
$this->authorize('delete-schedule', $server);
$this->repository->delete($schedule->id);
return response('', 204);
}
}

View File

@ -49,6 +49,7 @@ class Kernel extends HttpKernel
*/
protected $middleware = [
CheckForMaintenanceMode::class,
EncryptCookies::class,
ValidatePostSize::class,
TrimStrings::class,
ConvertEmptyStringsToNull::class,
@ -62,7 +63,6 @@ class Kernel extends HttpKernel
*/
protected $middlewareGroups = [
'web' => [
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
AuthenticateSession::class,
@ -73,7 +73,7 @@ class Kernel extends HttpKernel
RequireTwoFactorAuthentication::class,
],
'api' => [
'throttle:120,1',
'throttle:240,1',
ApiSubstituteBindings::class,
SetSessionDriver::class,
'api..key:' . ApiKey::TYPE_APPLICATION,
@ -81,9 +81,11 @@ class Kernel extends HttpKernel
AuthenticateIPAccess::class,
],
'client-api' => [
'throttle:60,1',
SubstituteClientApiBindings::class,
'throttle:240,1',
StartSession::class,
SetSessionDriver::class,
AuthenticateSession::class,
SubstituteClientApiBindings::class,
'api..key:' . ApiKey::TYPE_ACCOUNT,
AuthenticateIPAccess::class,
],

View File

@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Middleware\Api;
use Closure;
use Cake\Chronos\Chronos;
use Illuminate\Http\Request;
use Pterodactyl\Models\User;
use Pterodactyl\Models\ApiKey;
use Illuminate\Auth\AuthManager;
use Illuminate\Contracts\Encryption\Encrypter;
@ -58,13 +59,43 @@ class AuthenticateKey
*/
public function handle(Request $request, Closure $next, int $keyType)
{
if (is_null($request->bearerToken())) {
if (is_null($request->bearerToken()) && is_null($request->user())) {
throw new HttpException(401, null, null, ['WWW-Authenticate' => 'Bearer']);
}
$raw = $request->bearerToken();
$identifier = substr($raw, 0, ApiKey::IDENTIFIER_LENGTH);
$token = substr($raw, ApiKey::IDENTIFIER_LENGTH);
// This is a request coming through using cookies, we have an authenticated user not using
// an API key. Make some fake API key models and continue on through the process.
if (empty($raw) && $request->user() instanceof User) {
$model = (new ApiKey())->forceFill([
'user_id' => $request->user()->id,
'key_type' => ApiKey::TYPE_ACCOUNT,
]);
} else {
$model = $this->authenticateApiKey($raw, $keyType);
$this->auth->guard()->loginUsingId($model->user_id);
}
$request->attributes->set('api_key', $model);
return $next($request);
}
/**
* Authenticate an API key.
*
* @param string $key
* @param int $keyType
* @return \Pterodactyl\Models\ApiKey
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
protected function authenticateApiKey(string $key, int $keyType): ApiKey
{
$identifier = substr($key, 0, ApiKey::IDENTIFIER_LENGTH);
$token = substr($key, ApiKey::IDENTIFIER_LENGTH);
try {
$model = $this->repository->findFirstWhere([
@ -79,10 +110,8 @@ class AuthenticateKey
throw new AccessDeniedHttpException;
}
$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);
return $model;
}
}

View File

@ -1,60 +0,0 @@
<?php
namespace Pterodactyl\Http\Middleware\Api\Client;
use Closure;
use Illuminate\Http\Request;
use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class AuthenticateClientAccess
{
/**
* @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService
*/
private $keyProviderService;
/**
* AuthenticateClientAccess constructor.
*
* @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService
*/
public function __construct(DaemonKeyProviderService $keyProviderService)
{
$this->keyProviderService = $keyProviderService;
}
/**
* Authenticate that the currently authenticated user has permission
* to access the specified server. This only checks that the user is an
* admin, owner, or a subuser. You'll need to do more specific checks in
* the API calls to determine if they can perform different actions.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/
public function handle(Request $request, Closure $next)
{
if (is_null($request->user())) {
throw new AccessDeniedHttpException('A request must be made using an authenticated client.');
}
/** @var \Pterodactyl\Models\Server $server */
$server = $request->route()->parameter('server');
try {
$token = $this->keyProviderService->handle($server, $request->user());
} catch (RecordNotFoundException $exception) {
throw new NotFoundHttpException('The requested server could not be located.');
}
$request->attributes->set('server_token', $token);
return $next($request);
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace Pterodactyl\Http\Middleware\Api\Client\Server;
use Closure;
use Illuminate\Http\Request;
use Pterodactyl\Models\Server;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class AuthenticateServerAccess
{
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
private $repository;
/**
* AuthenticateServerAccess constructor.
*
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
*/
public function __construct(ServerRepositoryInterface $repository)
{
$this->repository = $repository;
}
/**
* Authenticate that this server exists and is not suspended or marked as installing.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
$server = $request->route()->parameter('server');
if (! $server instanceof Server) {
throw new NotFoundHttpException;
}
if ($server->suspended) {
throw new AccessDeniedHttpException('Cannot access a server that is marked as being suspended.');
}
if (! $server->isInstalled()) {
throw new ConflictHttpException('Server has not completed the installation process.');
}
$request->attributes->set('server', $server);
return $next($request);
}
}

View File

@ -4,9 +4,11 @@ namespace Pterodactyl\Http\Middleware\Api\Client;
use Closure;
use Illuminate\Container\Container;
use Pterodactyl\Contracts\Extensions\HashidsInterface;
use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
class SubstituteClientApiBindings extends ApiSubstituteBindings
{
@ -34,6 +36,20 @@ class SubstituteClientApiBindings extends ApiSubstituteBindings
}
});
$this->router->bind('database', function ($value) use ($request) {
try {
$id = Container::getInstance()->make(HashidsInterface::class)->decodeFirst($value);
return Container::getInstance()->make(DatabaseRepositoryInterface::class)->findFirstWhere([
['id', '=', $id],
]);
} catch (RecordNotFoundException $exception) {
$request->attributes->set('is_missing_model', true);
return null;
}
});
return parent::handle($request, $next);
}
}

View File

@ -4,17 +4,10 @@ namespace Pterodactyl\Http\Middleware\Api;
use Closure;
use Illuminate\Http\Request;
use Barryvdh\Debugbar\LaravelDebugbar;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
class SetSessionDriver
{
/**
* @var \Illuminate\Contracts\Foundation\Application
*/
private $app;
/**
* @var \Illuminate\Contracts\Config\Repository
*/
@ -23,12 +16,10 @@ class SetSessionDriver
/**
* SetSessionDriver constructor.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @param \Illuminate\Contracts\Config\Repository $config
* @param \Illuminate\Contracts\Config\Repository $config
*/
public function __construct(Application $app, ConfigRepository $config)
public function __construct(ConfigRepository $config)
{
$this->app = $app;
$this->config = $config;
}
@ -41,10 +32,6 @@ class SetSessionDriver
*/
public function handle(Request $request, Closure $next)
{
if ($this->config->get('app.debug')) {
$this->app->make(LaravelDebugbar::class)->disable();
}
$this->config->set('session.driver', 'array');
return $next($request);

View File

@ -10,6 +10,7 @@
namespace Pterodactyl\Http\Middleware;
use Closure;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Prologue\Alerts\AlertsMessageBag;
@ -24,27 +25,12 @@ class RequireTwoFactorAuthentication
*/
private $alert;
/**
* The names of routes that should be accessible without 2FA enabled.
*
* @var array
*/
protected $except = [
'account.security',
'account.security.revoke',
'account.security.totp',
'account.security.totp.set',
'account.security.totp.disable',
'auth.totp',
'auth.logout',
];
/**
* The route to redirect a user to to enable 2FA.
*
* @var string
*/
protected $redirectRoute = 'account.security';
protected $redirectRoute = 'account';
/**
* RequireTwoFactorAuthentication constructor.
@ -69,7 +55,8 @@ class RequireTwoFactorAuthentication
return $next($request);
}
if (in_array($request->route()->getName(), $this->except)) {
$current = $request->route()->getName();
if (in_array($current, ['auth', 'account']) || Str::startsWith($current, ['auth.', 'account.'])) {
return $next($request);
}

View File

@ -5,7 +5,6 @@ namespace Pterodactyl\Http\Middleware\Server;
use Closure;
use Illuminate\Http\Request;
use Pterodactyl\Models\Server;
use Illuminate\Contracts\Session\Session;
use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
@ -29,29 +28,21 @@ class AccessingValidServer
*/
private $response;
/**
* @var \Illuminate\Contracts\Session\Session
*/
private $session;
/**
* AccessingValidServer constructor.
*
* @param \Illuminate\Contracts\Config\Repository $config
* @param \Illuminate\Contracts\Routing\ResponseFactory $response
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
* @param \Illuminate\Contracts\Session\Session $session
*/
public function __construct(
ConfigRepository $config,
ResponseFactory $response,
ServerRepositoryInterface $repository,
Session $session
ServerRepositoryInterface $repository
) {
$this->config = $config;
$this->repository = $repository;
$this->response = $response;
$this->session = $session;
}
/**
@ -61,7 +52,6 @@ class AccessingValidServer
* @param \Closure $next
* @return \Illuminate\Http\Response|mixed
*
* @throws \Illuminate\Auth\AuthenticationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
@ -90,10 +80,6 @@ class AccessingValidServer
return $this->response->view('errors.installing', [], 409);
}
// Store the server in the session.
// @todo remove from session. use request attributes.
$this->session->now('server_data.model', $server);
// Add server to the request attributes. This will replace sessions
// as files are updated.
$request->attributes->set('server', $server);

View File

@ -126,10 +126,6 @@ abstract class ApplicationApiRequest extends FormRequest
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
/**
* @return bool
*/
protected function passesAuthorization()
{
// If we have already validated we do not need to call this function

View File

@ -0,0 +1,39 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Client\Account;
use Pterodactyl\Models\User;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
use Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException;
class UpdateEmailRequest extends ClientApiRequest
{
/**
* @return bool
*
* @throws \Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException
*/
public function authorize(): bool
{
if (! parent::authorize()) {
return false;
}
// Verify password matches when changing password or email.
if (! password_verify($this->input('password'), $this->user()->password)) {
throw new InvalidPasswordProvidedException(trans('validation.internal.invalid_password'));
}
return true;
}
/**
* @return array
*/
public function rules(): array
{
$rules = User::getUpdateRulesForId($this->user()->id);
return ['email' => $rules['email']];
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Client\Account;
use Pterodactyl\Models\User;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
use Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException;
class UpdatePasswordRequest extends ClientApiRequest
{
/**
* @return bool
*
* @throws \Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException
*/
public function authorize(): bool
{
if (! parent::authorize()) {
return false;
}
// Verify password matches when changing password or email.
if (! password_verify($this->input('current_password'), $this->user()->password)) {
throw new InvalidPasswordProvidedException(trans('validation.internal.invalid_password'));
}
return true;
}
/**
* @return array
*/
public function rules(): array
{
$rules = User::getUpdateRulesForId($this->user()->id);
return ['password' => array_merge($rules['password'], ['confirmed'])];
}
}

View File

@ -2,18 +2,23 @@
namespace Pterodactyl\Http\Requests\Api\Client;
use Pterodactyl\Models\Server;
use Pterodactyl\Contracts\Http\ClientPermissionsRequest;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
abstract class ClientApiRequest extends ApplicationApiRequest
{
/**
* Determine if the current user is authorized to perform
* the requested action against the API.
* Determine if the current user is authorized to perform the requested action against the API.
*
* @return bool
*/
public function authorize(): bool
{
if ($this instanceof ClientPermissionsRequest || method_exists($this, 'permission')) {
return $this->user()->can($this->permission(), $this->getModel(Server::class));
}
return true;
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Databases;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Database;
use Pterodactyl\Contracts\Http\ClientPermissionsRequest;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
class DeleteDatabaseRequest extends ClientApiRequest implements ClientPermissionsRequest
{
/**
* @return string
*/
public function permission(): string
{
return 'delete-database';
}
/**
* @return bool
*/
public function resourceExists(): bool
{
return $this->getModel(Server::class)->id === $this->getModel(Database::class)->server_id;
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Databases;
use Pterodactyl\Contracts\Http\ClientPermissionsRequest;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
class GetDatabasesRequest extends ClientApiRequest implements ClientPermissionsRequest
{
/**
* @return string
*/
public function permission(): string
{
return 'view-databases';
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Databases;
use Pterodactyl\Contracts\Http\ClientPermissionsRequest;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
class StoreDatabaseRequest extends ClientApiRequest implements ClientPermissionsRequest
{
/**
* @return string
*/
public function permission(): string
{
return 'create-database';
}
/**
* @return array
*/
public function rules(): array
{
return [
'database' => 'required|alpha_dash|min:1|max:100',
'remote' => 'required|string|regex:/^[0-9%.]{1,15}$/',
];
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Files;
use Pterodactyl\Models\Server;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
class DownloadFileRequest extends ClientApiRequest
{
/**
* Ensure that the user making this request has permission to download files
* from this server.
*
* @return bool
*/
public function authorize(): bool
{
return $this->user()->can('download-files', $this->getModel(Server::class));
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Files;
use Pterodactyl\Models\Server;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
class ListFilesRequest extends ClientApiRequest
{
/**
* Check that the user making this request to the API is authorized to list all
* of the files that exist for a given server.
*
* @return bool
*/
public function authorize(): bool
{
return $this->user()->can('list-files', $this->getModel(Server::class));
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Network;
use Pterodactyl\Models\Server;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
class GetNetworkRequest extends ClientApiRequest
{
/**
* Check that the user has permission to view the allocations for
* this server.
*
* @return bool
*/
public function authorize(): bool
{
return $this->user()->can('view-allocations', $this->getModel(Server::class));
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Pterodactyl\Http\Requests\Auth;
use Illuminate\Foundation\Http\FormRequest;
class LoginCheckpointRequest extends FormRequest
{
/**
* Determine if the request is authorized.
*
* @return bool
*/
public function authorize(): bool
{
return true;
}
/**
* Rules to apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'confirmation_token' => 'required|string',
'authentication_code' => 'required|int',
];
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Pterodactyl\Http\Requests\Auth;
use Illuminate\Foundation\Http\FormRequest;
class LoginRequest extends FormRequest
{
/**
* @return bool
*/
public function authorized(): bool
{
return true;
}
/**
* @return array
*/
public function rules(): array
{
return [
'user' => 'required|string|min:1',
'password' => 'required|string',
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Pterodactyl\Http\Requests\Auth;
use Illuminate\Foundation\Http\FormRequest;
class ResetPasswordRequest extends FormRequest
{
/**
* @return bool
*/
public function authorize(): bool
{
return true;
}
/**
* @return array
*/
public function rules(): array
{
return [
'token' => 'required|string',
'email' => 'required|email',
'password' => 'required|string|confirmed|min:8',
];
}
}

View File

@ -28,7 +28,7 @@ class AccountDataFormRequest extends FrontendUserFormRequest
// Verify password matches when changing password or email.
if (in_array($this->input('do_action'), ['password', 'email'])) {
if (! password_verify($this->input('current_password'), $this->user()->password)) {
throw new InvalidPasswordProvidedException(trans('base.account.invalid_password'));
throw new InvalidPasswordProvidedException(trans('validation.internal.invalid_password'));
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace Pterodactyl\Http\ViewComposers;
use Illuminate\View\View;
use Pterodactyl\Services\Helpers\AssetHashService;
class AssetComposer
{
/**
* @var \Pterodactyl\Services\Helpers\AssetHashService
*/
private $assetHashService;
/**
* AssetComposer constructor.
*
* @param \Pterodactyl\Services\Helpers\AssetHashService $assetHashService
*/
public function __construct(AssetHashService $assetHashService)
{
$this->assetHashService = $assetHashService;
}
/**
* Provide access to the asset service in the views.
*
* @param \Illuminate\View\View $view
*/
public function compose(View $view)
{
$view->with('asset', $this->assetHashService);
}
}

View File

@ -132,6 +132,16 @@ class Node extends Model implements CleansAttributes, ValidableContract
'maintenance_mode' => false,
];
/**
* Get the connection address to use when making calls to this node.
*
* @return string
*/
public function getConnectionAddress(): string
{
return sprintf('%s://%s:%s', $this->scheme, $this->fqdn, $this->daemonListen);
}
/**
* Returns the configuration in JSON format.
*

View File

@ -143,6 +143,14 @@ class Server extends Model implements CleansAttributes, ValidableContract
return Schema::getColumnListing($this->getTable());
}
/**
* @return bool
*/
public function isInstalled(): bool
{
return $this->installed === 1;
}
/**
* Gets the user who owns the server.
*

View File

@ -5,6 +5,7 @@ namespace Pterodactyl\Models;
use Sofa\Eloquence\Eloquence;
use Sofa\Eloquence\Validable;
use Pterodactyl\Rules\Username;
use Illuminate\Support\Collection;
use Illuminate\Validation\Rules\In;
use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
@ -177,6 +178,16 @@ class User extends Model implements
return $rules;
}
/**
* Return the user model in a format that can be passed over to Vue templates.
*
* @return array
*/
public function toVueObject(): array
{
return (new Collection($this->toArray()))->except(['id', 'external_id'])->toArray();
}
/**
* Send the password reset notification.
*

View File

@ -3,7 +3,7 @@
namespace Pterodactyl\Providers;
use Illuminate\Support\ServiceProvider;
use Pterodactyl\Repositories\Daemon\FileRepository;
use Pterodactyl\Repositories\Wings\FileRepository;
use Pterodactyl\Repositories\Daemon\PowerRepository;
use Pterodactyl\Repositories\Eloquent\EggRepository;
use Pterodactyl\Repositories\Eloquent\NestRepository;

View File

@ -22,20 +22,21 @@ class RouteServiceProvider extends ServiceProvider
public function map()
{
Route::middleware(['web', 'auth', 'csrf'])
->namespace($this->namespace . '\Base')
->group(base_path('routes/base.php'));
->namespace($this->namespace . '\Base')
->group(base_path('routes/base.php'));
Route::middleware(['web', 'auth', 'admin', 'csrf'])->prefix('/admin')
->namespace($this->namespace . '\Admin')
->group(base_path('routes/admin.php'));
->namespace($this->namespace . '\Admin')
->group(base_path('routes/admin.php'));
Route::middleware(['web', 'csrf'])->prefix('/auth')
->namespace($this->namespace . '\Auth')
->group(base_path('routes/auth.php'));
->namespace($this->namespace . '\Auth')
->group(base_path('routes/auth.php'));
Route::middleware(['web', 'csrf', 'auth', 'server', 'subuser.auth', 'node.maintenance'])->prefix('/server/{server}')
->namespace($this->namespace . '\Server')
->group(base_path('routes/server.php'));
Route::middleware(['web', 'csrf', 'auth', 'server', 'subuser.auth', 'node.maintenance'])
->prefix('/api/server/{server}')
->namespace($this->namespace . '\Server')
->group(base_path('routes/server.php'));
Route::middleware(['api'])->prefix('/api/application')
->namespace($this->namespace . '\Api\Application')
@ -50,7 +51,7 @@ class RouteServiceProvider extends ServiceProvider
->group(base_path('routes/api-remote.php'));
Route::middleware(['web', 'daemon-old'])->prefix('/daemon')
->namespace($this->namespace . '\Daemon')
->group(base_path('routes/daemon.php'));
->namespace($this->namespace . '\Daemon')
->group(base_path('routes/daemon.php'));
}
}

View File

@ -3,6 +3,7 @@
namespace Pterodactyl\Providers;
use Illuminate\Support\ServiceProvider;
use Pterodactyl\Http\ViewComposers\AssetComposer;
use Pterodactyl\Http\ViewComposers\ServerListComposer;
use Pterodactyl\Http\ViewComposers\Server\ServerDataComposer;
@ -13,6 +14,8 @@ class ViewComposerServiceProvider extends ServiceProvider
*/
public function boot()
{
$this->app->make('view')->composer('*', AssetComposer::class);
$this->app->make('view')->composer('server.*', ServerDataComposer::class);
// Add data to make the sidebar work when viewing a server.

View File

@ -84,33 +84,6 @@ class FileRepository extends BaseRepository implements FileRepositoryInterface
{
$response = $this->getHttpClient()->request('GET', sprintf('server/directory/%s', rawurlencode($path)));
$contents = json_decode($response->getBody());
$files = $folders = [];
foreach ($contents as $value) {
if ($value->directory) {
array_push($folders, [
'entry' => $value->name,
'directory' => trim($path, '/'),
'size' => null,
'date' => strtotime($value->modified),
'mime' => $value->mime,
]);
} elseif ($value->file) {
array_push($files, [
'entry' => $value->name,
'directory' => trim($path, '/'),
'extension' => str_replace('\\', '/', pathinfo($value->name, PATHINFO_EXTENSION)),
'size' => human_readable($value->size),
'date' => strtotime($value->modified),
'mime' => $value->mime,
]);
}
}
return [
'files' => $files,
'folders' => $folders,
];
return json_decode($response->getBody());
}
}

View File

@ -76,7 +76,7 @@ class DatabaseRepository extends EloquentRepository implements DatabaseRepositor
*/
public function getDatabasesForServer(int $server): Collection
{
return $this->getBuilder()->where('server_id', $server)->get($this->getColumns());
return $this->getBuilder()->with('host')->where('server_id', $server)->get($this->getColumns());
}
/**

View File

@ -0,0 +1,33 @@
<?php
namespace Pterodactyl\Repositories\Wings;
use GuzzleHttp\Client;
use Pterodactyl\Repositories\Daemon\BaseRepository;
use Pterodactyl\Contracts\Repository\Daemon\BaseRepositoryInterface;
abstract class BaseWingsRepository extends BaseRepository implements BaseRepositoryInterface
{
/**
* Return an instance of the Guzzle HTTP Client to be used for requests.
*
* @param array $headers
* @return \GuzzleHttp\Client
*/
public function getHttpClient(array $headers = []): Client
{
// We're just going to extend the parent client here since that logic is already quite
// sound and does everything we need it to aside from provide the correct base URL
// and authentication headers.
$client = parent::getHttpClient($headers);
return new Client(array_merge($client->getConfig(), [
'base_uri' => $this->getNode()->getConnectionAddress(),
'headers' => [
'Authorization' => 'Bearer ' . ($this->getToken() ?? $this->getNode()->daemonSecret),
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
]));
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace Pterodactyl\Repositories\Wings;
use stdClass;
use Psr\Http\Message\ResponseInterface;
use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface;
class FileRepository extends BaseWingsRepository implements FileRepositoryInterface
{
/**
* Return stat information for a given file.
*
* @param string $path
* @return \stdClass
*
* @throws \GuzzleHttp\Exception\TransferException
*/
public function getFileStat(string $path): stdClass
{
// TODO: Implement getFileStat() method.
}
/**
* Return the contents of a given file if it can be edited in the Panel.
*
* @param string $path
* @return string
*
* @throws \GuzzleHttp\Exception\TransferException
*/
public function getContent(string $path): string
{
// TODO: Implement getContent() method.
}
/**
* Save new contents to a given file.
*
* @param string $path
* @param string $content
* @return \Psr\Http\Message\ResponseInterface
*
* @throws \GuzzleHttp\Exception\TransferException
*/
public function putContent(string $path, string $content): ResponseInterface
{
// TODO: Implement putContent() method.
}
/**
* Return a directory listing for a given path.
*
* @param string $path
* @return array
*
* @throws \GuzzleHttp\Exception\TransferException
*/
public function getDirectory(string $path): array
{
$response = $this->getHttpClient()->get(
// Reason for the path check is because it is unnecessary on the Daemon but we need
// to respect the interface.
sprintf('/api/servers/%s/files/list/%s', $this->getServer()->uuid, $path === '/' ? '' : $path)
);
return json_decode($response->getBody(), true);
}
}

View File

@ -0,0 +1,141 @@
<?php
namespace Pterodactyl\Services\Helpers;
use Illuminate\Filesystem\FilesystemManager;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Cache\Repository as CacheRepository;
class AssetHashService
{
/**
* Location of the manifest file generated by gulp.
*/
public const MANIFEST_PATH = './assets/manifest.json';
/**
* @var \Illuminate\Contracts\Cache\Repository
*/
private $cache;
/**
* @var \Illuminate\Contracts\Filesystem\Filesystem
*/
private $filesystem;
/**
* @var \Illuminate\Contracts\Foundation\Application
*/
private $application;
/**
* @var null|array
*/
protected static $manifest;
/**
* AssetHashService constructor.
*
* @param \Illuminate\Contracts\Foundation\Application $application
* @param \Illuminate\Contracts\Cache\Repository $cache
* @param \Illuminate\Filesystem\FilesystemManager $filesystem
*/
public function __construct(Application $application, CacheRepository $cache, FilesystemManager $filesystem)
{
$this->application = $application;
$this->cache = $cache;
$this->filesystem = $filesystem->createLocalDriver(['root' => public_path()]);
}
/**
* Modify a URL to append the asset hash.
*
* @param string $resource
* @return string
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function url(string $resource): string
{
$file = last(explode('/', $resource));
$data = array_get($this->manifest(), $file, $file);
return str_replace($file, array_get($data, 'src', $file), $resource);
}
/**
* Return the data integrity hash for a resource.
*
* @param string $resource
* @return string
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function integrity(string $resource): string
{
$file = last(explode('/', $resource));
$data = array_get($this->manifest(), $file, $file);
return array_get($data, 'integrity', '');
}
/**
* Return a built CSS import using the provided URL.
*
* @param string $resource
* @return string
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function css(string $resource): string
{
return '<link href="' . $this->url($resource) . '"
rel="stylesheet preload"
as="style"
crossorigin="anonymous"
integrity="' . $this->integrity($resource) . '"
referrerpolicy="no-referrer">';
}
/**
* Return a built JS import using the provided URL.
*
* @param string $resource
* @return string
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function js(string $resource): string
{
return '<script src="' . $this->url($resource) . '"
integrity="' . $this->integrity($resource) . '"
crossorigin="anonymous"></script>';
}
/**
* Get the asset manifest and store it in the cache for quicker lookups.
*
* @return array
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
protected function manifest(): array
{
if (! is_null(self::$manifest)) {
return self::$manifest;
}
// Skip checking the cache if we are not in production.
if ($this->application->environment() === 'production') {
$stored = $this->cache->get('Core:AssetManifest');
if (! is_null($stored)) {
return self::$manifest = $stored;
}
}
$contents = json_decode($this->filesystem->get(self::MANIFEST_PATH), true);
$this->cache->put('Core:AssetManifest', $contents, 1440);
return self::$manifest = $contents;
}
}

View File

@ -10,6 +10,7 @@
namespace Pterodactyl\Services\Users;
use Pterodactyl\Models\User;
use Illuminate\Support\Collection;
use PragmaRX\Google2FAQRCode\Google2FA;
use Illuminate\Contracts\Encryption\Encrypter;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
@ -62,12 +63,12 @@ class TwoFactorSetupService
* QR code image.
*
* @param \Pterodactyl\Models\User $user
* @return string
* @return \Illuminate\Support\Collection
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function handle(User $user): string
public function handle(User $user): Collection
{
$secret = $this->google2FA->generateSecretKey($this->config->get('pterodactyl.auth.2fa.bytes'));
$image = $this->google2FA->getQRCodeInline($this->config->get('app.name'), $user->email, $secret);
@ -76,6 +77,9 @@ class TwoFactorSetupService
'totp_secret' => $this->encrypter->encrypt($secret),
]);
return $image;
return new Collection([
'image' => $image,
'secret' => $secret,
]);
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Pterodactyl\Transformers\Api\Client;
use Pterodactyl\Models\User;
class AccountTransformer extends BaseClientTransformer
{
/**
* Return the resource name for the JSONAPI output.
*
* @return string
*/
public function getResourceName(): string
{
return 'user';
}
/**
* Return basic information about the currently logged in user.
*
* @param \Pterodactyl\Models\User $model
* @return array
*/
public function transform(User $model)
{
return [
'id' => $model->id,
'admin' => $model->root_admin,
'username' => $model->username,
'email' => $model->email,
'first_name' => $model->name_first,
'last_name' => $model->name_last,
'language' => $model->language,
];
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Pterodactyl\Transformers\Api\Client;
use Pterodactyl\Models\Allocation;
class AllocationTransformer extends BaseClientTransformer
{
/**
* Return the resource name for the JSONAPI output.
*
* @return string
*/
public function getResourceName(): string
{
return 'allocation';
}
/**
* Return basic information about the currently logged in user.
*
* @param \Pterodactyl\Models\Allocation $model
* @return array
*/
public function transform(Allocation $model)
{
$model->loadMissing('server');
return [
'ip' => $model->ip,
'alias' => $model->ip_alias,
'port' => $model->port,
'default' => $model->getRelation('server')->allocation_id === $model->id,
];
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace Pterodactyl\Transformers\Api\Client;
use Pterodactyl\Models\Database;
use League\Fractal\Resource\Item;
use Illuminate\Contracts\Encryption\Encrypter;
use Pterodactyl\Contracts\Extensions\HashidsInterface;
class DatabaseTransformer extends BaseClientTransformer
{
protected $availableIncludes = ['password'];
/**
* @var \Illuminate\Contracts\Encryption\Encrypter
*/
private $encrypter;
/**
* @var \Pterodactyl\Contracts\Extensions\HashidsInterface
*/
private $hashids;
/**
* Handle dependency injection.
*
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
* @param \Pterodactyl\Contracts\Extensions\HashidsInterface $hashids
*/
public function handle(Encrypter $encrypter, HashidsInterface $hashids)
{
$this->encrypter = $encrypter;
$this->hashids = $hashids;
}
/**
* @return string
*/
public function getResourceName(): string
{
return Database::RESOURCE_NAME;
}
/**
* @param \Pterodactyl\Models\Database $model
* @return array
*/
public function transform(Database $model): array
{
$model->loadMissing('host');
return [
'id' => $this->hashids->encode($model->id),
'host' => [
'address' => $model->getRelation('host')->host,
'port' => $model->getRelation('host')->port,
],
'name' => $model->database,
'username' => $model->username,
'connections_from' => $model->remote,
];
}
/**
* Include the database password in the request.
*
* @param \Pterodactyl\Models\Database $model
* @return \League\Fractal\Resource\Item
*/
public function includePassword(Database $model): Item
{
return $this->item($model, function (Database $model) {
return [
'password' => $this->encrypter->decrypt($model->password),
];
}, 'database_password');
}
}

View File

@ -28,7 +28,12 @@ class ServerTransformer extends BaseClientTransformer
'identifier' => $server->uuidShort,
'uuid' => $server->uuid,
'name' => $server->name,
'node' => $server->node->name,
'description' => $server->description,
'allocation' => [
'ip' => $server->allocation->alias,
'port' => $server->allocation->port,
],
'limits' => [
'memory' => $server->memory,
'swap' => $server->swap,

View File

@ -3,6 +3,8 @@
namespace Pterodactyl\Transformers\Api\Client;
use Pterodactyl\Models\Server;
use GuzzleHttp\Exception\RequestException;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface;
class StatsTransformer extends BaseClientTransformer
@ -36,6 +38,8 @@ class StatsTransformer extends BaseClientTransformer
*
* @param \Pterodactyl\Models\Server $model
* @return array
*
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function transform(Server $model)
{
@ -61,7 +65,10 @@ class StatsTransformer extends BaseClientTransformer
'disk' => [
'current' => round(object_get($object, 'proc.disk.used', 0)),
'limit' => floatval($model->disk),
'io' => $model->io,
],
'installed' => $model->installed === 1,
'suspended' => (bool) $model->suspended,
];
}

View File

@ -48,10 +48,13 @@
"filp/whoops": "^2.1",
"friendsofphp/php-cs-fixer": "^2.11.1",
"fzaninotto/faker": "^1.6",
"laravel/dusk": "^3.0",
"martinlindhe/laravel-vue-i18n-generator": "^0.1.28",
"mockery/mockery": "^1.0",
"nunomaduro/collision": "^2.0",
"php-mock/php-mock-phpunit": "^2.1",
"phpunit/phpunit": "~7.0"
"phpunit/phpunit": "~7.0",
"tightenco/ziggy": "^0.5.0"
},
"autoload": {
"classmap": [
@ -66,6 +69,7 @@
},
"autoload-dev": {
"psr-4": {
"Pterodactyl\\Tests\\Browser\\": "tests/Browser",
"Pterodactyl\\Tests\\Integration\\": "tests/Integration",
"Tests\\": "tests/"
}

1119
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,7 @@ return [
|
*/
'lifetime' => env('SESSION_LIFETIME', 10080),
'lifetime' => env('SESSION_LIFETIME', 720),
'expire_on_close' => false,

View File

@ -0,0 +1,50 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Laravel translations path
|--------------------------------------------------------------------------
|
| The default path where the translations are stored by Laravel.
| Note: the path will be prepended to point to the App directory.
|
*/
'langPath' => '/resources/lang',
/*
|--------------------------------------------------------------------------
| Laravel translation files
|--------------------------------------------------------------------------
|
| You can choose which translation files to be generated.
| Note: leave this empty for all the translation files to be generated.
|
*/
'langFiles' => [],
/*
|--------------------------------------------------------------------------
| Output file
|--------------------------------------------------------------------------
|
| The javascript path where I will place the generated file.
| Note: the path will be prepended to point to the App directory.
|
*/
'jsPath' => '/resources/lang/i18n',
'jsFile' => '/resources/lang/locales.js',
/*
|--------------------------------------------------------------------------
| i18n library
|--------------------------------------------------------------------------
|
| Specify the library you use for localization.
| Options are vue-i18n or vuex-i18n.
|
*/
'i18nLib' => 'vuex-i18n',
];

View File

@ -1,11 +1,73 @@
{
"name": "pterodactyl-panel",
"devDependencies": {
"babel-cli": "6.18.0",
"babel-plugin-transform-strict-mode": "^6.18.0",
"babel-preset-es2015": "6.18.0"
},
"scripts": {
"build": "./node_modules/babel-cli/bin/babel.js public/themes/pterodactyl/js/frontend/files/src --source-maps --out-file public/themes/pterodactyl/js/frontend/files/filemanager.min.js"
}
"name": "pterodactyl-panel",
"dependencies": {
"axios": "^0.18.0",
"date-fns": "^1.29.0",
"feather-icons": "^4.10.0",
"jquery": "^3.3.1",
"lodash": "^4.17.11",
"socket.io-client": "^2.2.0",
"vee-validate": "^2.1.7",
"vue": "^2.6.4",
"vue-axios": "^2.1.1",
"vue-i18n": "^8.6.0",
"vue-router": "^3.0.1",
"vuex": "^3.0.1",
"vuex-router-sync": "^5.0.0",
"ws-wrapper": "^2.0.0",
"xterm": "^3.5.1"
},
"devDependencies": {
"@babel/cli": "^7.2.3",
"@babel/core": "^7.2.2",
"@babel/plugin-proposal-class-properties": "^7.3.0",
"@babel/plugin-proposal-object-rest-spread": "^7.3.1",
"@babel/preset-env": "^7.3.1",
"@types/feather-icons": "^4.7.0",
"@types/lodash": "^4.14.119",
"@types/node": "^10.12.15",
"@types/socket.io-client": "^1.4.32",
"@types/webpack-env": "^1.13.6",
"babel-loader": "^8.0.5",
"clean-webpack-plugin": "^0.1.19",
"css-loader": "^2.1.0",
"cssnano": "^4.0.3",
"fork-ts-checker-webpack-plugin": "^0.5.2",
"glob-all": "^3.1.0",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.5.0",
"postcss": "^6.0.21",
"postcss-import": "^11.1.0",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^3.4.0",
"precss": "^3.1.2",
"purgecss-webpack-plugin": "^1.1.0",
"resolve-url-loader": "^3.0.0",
"style-loader": "^0.23.1",
"tailwindcss": "^0.7.4",
"ts-loader": "^5.3.3",
"typescript": "^3.3.1",
"uglifyjs-webpack-plugin": "^2.1.1",
"vue-devtools": "^3.1.9",
"vue-feather-icons": "^4.7.1",
"vue-loader": "^15.6.2",
"vue-mc": "^0.2.4",
"vue-template-compiler": "^2.6.4",
"vueify-insert-css": "^1.0.0",
"webpack": "^4.29.0",
"webpack-assets-manifest": "^3.1.1",
"webpack-cli": "^3.0.2",
"webpack-dev-server": "^3.1.14",
"webpack-manifest-plugin": "^2.0.3",
"webpack-shell-plugin": "^0.5.0",
"webpack-stream": "^4.0.3"
},
"scripts": {
"watch": "NODE_ENV=development ./node_modules/.bin/webpack --watch --progress",
"build": "NODE_ENV=development ./node_modules/.bin/webpack --progress",
"build:production": "NODE_ENV=production ./node_modules/.bin/webpack",
"serve": "NODE_ENV=development webpack-dev-server --host 0.0.0.0 --hot",
"v:serve": "PUBLIC_PATH=http://pterodactyl.test:8080 yarn run serve",
"compile:assets": "php artisan vue-i18n:generate & php artisan ziggy:generate resources/assets/scripts/helpers/ziggy.js"
}
}

21
phpunit.dusk.xml Normal file
View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Browser Test Suite">
<directory suffix="Test.php">./tests/Browser</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./app</directory>
</whitelist>
</filter>
</phpunit>

View File

@ -10,6 +10,9 @@
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Browser">
<directory suffix="Test.php">./tests/Browser/Processes</directory>
</testsuite>
<testsuite name="Integration">
<directory suffix="Test.php">./tests/Integration</directory>
</testsuite>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Some files were not shown because too many files have changed in this diff Show More