diff --git a/CHANGELOG.md b/CHANGELOG.md index 39a3a8b18..6b4508658 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,13 @@ This project follows [Semantic Versioning](http://semver.org) guidelines. * `[beta.1]` — Fixes missing check in environment setup that would leave the Hashids salt empty. * `[beta.1]` — Fixes bug preventing loading of allocations when trying to create a new server. * `[beta.1]` — Fixes bug causing inability to create new servers on the Panel. +* `[beta.1]` — Fixes bug causing inability to delete an allocation due to misconfigured JS. +* `[beta.1]` — Fixes bug causing inability to set the IP alias for an allocation to an empty value. +* `[beta.1]` — Fixes bug that caused startup changes to not propigate to the server correctly on the first save. + +### Changed +* Moved Docker image setting to be on the startup management page for a server rather than the details page. This value changes based on the Nest and Egg that are selected. +* Two-Factor authentication tokens are now 32 bytes in length, and are stored encrypted at rest in the database. ## v0.7.0-beta.1 (Derelict Dermodactylus) ### Added diff --git a/app/Console/Commands/Maintenance/CleanServiceBackupFilesCommand.php b/app/Console/Commands/Maintenance/CleanServiceBackupFilesCommand.php index 1cbe6090d..f3982921e 100644 --- a/app/Console/Commands/Maintenance/CleanServiceBackupFilesCommand.php +++ b/app/Console/Commands/Maintenance/CleanServiceBackupFilesCommand.php @@ -15,6 +15,8 @@ use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory; class CleanServiceBackupFilesCommand extends Command { + const BACKUP_THRESHOLD_MINUTES = 5; + /** * @var \Carbon\Carbon */ @@ -58,7 +60,7 @@ class CleanServiceBackupFilesCommand extends Command collect($files)->each(function ($file) { $lastModified = $this->carbon->timestamp($this->disk->lastModified($file)); - if ($lastModified->diffInMinutes($this->carbon->now()) > 5) { + if ($lastModified->diffInMinutes($this->carbon->now()) > self::BACKUP_THRESHOLD_MINUTES) { $this->disk->delete($file); $this->info(trans('command/messages.maintenance.deleting_service_backup', ['file' => $file])); } diff --git a/app/Contracts/Repository/ServerRepositoryInterface.php b/app/Contracts/Repository/ServerRepositoryInterface.php index e074d7059..2fb8349e3 100644 --- a/app/Contracts/Repository/ServerRepositoryInterface.php +++ b/app/Contracts/Repository/ServerRepositoryInterface.php @@ -95,14 +95,15 @@ interface ServerRepositoryInterface extends RepositoryInterface, SearchableInter public function getWithDatabases($id); /** - * Return data about the daemon service in a consumable format. + * Get data for use when updating a server on the Daemon. Returns an array of + * the egg and pack UUID which are used for build and rebuild. Only loads relations + * if they are missing, or refresh is set to true. * - * @param int $id + * @param \Pterodactyl\Models\Server $server + * @param bool $refresh * @return array - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function getDaemonServiceData($id); + public function getDaemonServiceData(Server $server, bool $refresh = false): array; /** * Return an array of server IDs that a given user can access based on owner and subuser permissions. diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 203e7058c..0bf3610f7 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -410,25 +410,6 @@ class ServersController extends Controller return redirect()->route('admin.servers.view.details', $server->id); } - /** - * Set the new docker container for a server. - * - * @param \Illuminate\Http\Request $request - * @param \Pterodactyl\Models\Server $server - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function setContainer(Request $request, Server $server) - { - $this->detailsModificationService->setDockerImage($server, $request->input('docker_image')); - $this->alert->success(trans('admin/server.alerts.docker_image_updated'))->flash(); - - return redirect()->route('admin.servers.view.details', $server->id); - } - /** * Toggles the install status for a server. * diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 12f3df533..9fab7b53e 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -202,7 +202,7 @@ class LoginController extends Controller return $this->sendFailedLoginResponse($request); } - if (! $G2FA->verifyKey($user->totp_secret, $request->input('2fa_token'), 2)) { + if (! $G2FA->verifyKey(Crypt::decrypt($user->totp_secret), $request->input('2fa_token'), 2)) { event(new \Illuminate\Auth\Events\Failed($user, $credentials)); return $this->sendFailedLoginResponse($request); diff --git a/app/Http/Controllers/Base/SecurityController.php b/app/Http/Controllers/Base/SecurityController.php index d22c0ddb9..62f07738c 100644 --- a/app/Http/Controllers/Base/SecurityController.php +++ b/app/Http/Controllers/Base/SecurityController.php @@ -27,7 +27,6 @@ namespace Pterodactyl\Http\Controllers\Base; use Illuminate\Http\Request; use Prologue\Alerts\AlertsMessageBag; -use Illuminate\Contracts\Session\Session; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Users\TwoFactorSetupService; use Pterodactyl\Services\Users\ToggleTwoFactorService; @@ -52,11 +51,6 @@ class SecurityController extends Controller */ protected $repository; - /** - * @var \Illuminate\Contracts\Session\Session - */ - protected $session; - /** * @var \Pterodactyl\Services\Users\ToggleTwoFactorService */ @@ -72,7 +66,6 @@ class SecurityController extends Controller * * @param \Prologue\Alerts\AlertsMessageBag $alert * @param \Illuminate\Contracts\Config\Repository $config - * @param \Illuminate\Contracts\Session\Session $session * @param \Pterodactyl\Contracts\Repository\SessionRepositoryInterface $repository * @param \Pterodactyl\Services\Users\ToggleTwoFactorService $toggleTwoFactorService * @param \Pterodactyl\Services\Users\TwoFactorSetupService $twoFactorSetupService @@ -80,7 +73,6 @@ class SecurityController extends Controller public function __construct( AlertsMessageBag $alert, ConfigRepository $config, - Session $session, SessionRepositoryInterface $repository, ToggleTwoFactorService $toggleTwoFactorService, TwoFactorSetupService $twoFactorSetupService @@ -88,7 +80,6 @@ class SecurityController extends Controller $this->alert = $alert; $this->config = $config; $this->repository = $repository; - $this->session = $session; $this->toggleTwoFactorService = $toggleTwoFactorService; $this->twoFactorSetupService = $twoFactorSetupService; } @@ -122,7 +113,9 @@ class SecurityController extends Controller */ public function generateTotp(Request $request) { - return response()->json($this->twoFactorSetupService->handle($request->user())); + return response()->json([ + 'qrImage' => $this->twoFactorSetupService->handle($request->user()), + ]); } /** diff --git a/app/Http/Requests/Admin/Node/AllocationAliasFormRequest.php b/app/Http/Requests/Admin/Node/AllocationAliasFormRequest.php index 6d607211c..2552114ab 100644 --- a/app/Http/Requests/Admin/Node/AllocationAliasFormRequest.php +++ b/app/Http/Requests/Admin/Node/AllocationAliasFormRequest.php @@ -19,7 +19,7 @@ class AllocationAliasFormRequest extends AdminFormRequest public function rules() { return [ - 'alias' => 'required|nullable|string', + 'alias' => 'present|nullable|string', 'allocation_id' => 'required|numeric|exists:allocations,id', ]; } diff --git a/app/Models/Allocation.php b/app/Models/Allocation.php index bb77647d9..2fce57e84 100644 --- a/app/Models/Allocation.php +++ b/app/Models/Allocation.php @@ -60,7 +60,7 @@ class Allocation extends Model implements CleansAttributes, ValidableContract 'node_id' => 'exists:nodes,id', 'ip' => 'ip', 'port' => 'numeric|between:1024,65553', - 'alias' => 'string', + 'ip_alias' => 'nullable|string', 'server_id' => 'nullable|exists:servers,id', ]; diff --git a/app/Models/User.php b/app/Models/User.php index 7b09165aa..39e4a0a03 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -63,6 +63,7 @@ class User extends Model implements 'language', 'use_totp', 'totp_secret', + 'totp_authenticated_at', 'gravatar', 'root_admin', ]; @@ -78,6 +79,11 @@ class User extends Model implements 'gravatar' => 'boolean', ]; + /** + * @var array + */ + protected $dates = [self::CREATED_AT, self::UPDATED_AT, 'totp_authenticated_at']; + /** * The attributes excluded from the model's JSON form. * diff --git a/app/Policies/APIKeyPolicy.php b/app/Policies/APIKeyPolicy.php index 7ca4e0a91..69ce45c04 100644 --- a/app/Policies/APIKeyPolicy.php +++ b/app/Policies/APIKeyPolicy.php @@ -13,7 +13,6 @@ use Cache; use Carbon; use Pterodactyl\Models\User; use Pterodactyl\Models\APIKey as Key; -use Pterodactyl\Models\APIPermission as Permission; class APIKeyPolicy { diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 1f48d33da..c7c928f1a 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Providers; -use Illuminate\Support\Facades\Event; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; class EventServiceProvider extends ServiceProvider diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 96bfb2ec1..57ae43fad 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Providers; -use Pterodactyl\Models\User; use Illuminate\Support\Facades\Route; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; diff --git a/app/Repositories/Eloquent/EloquentRepository.php b/app/Repositories/Eloquent/EloquentRepository.php index ea333d2dc..d94cd5cac 100644 --- a/app/Repositories/Eloquent/EloquentRepository.php +++ b/app/Repositories/Eloquent/EloquentRepository.php @@ -10,7 +10,7 @@ namespace Pterodactyl\Repositories\Eloquent; use Webmozart\Assert\Assert; -use Pterodactyl\Repository\Repository; +use Pterodactyl\Repositories\Repository; use Illuminate\Database\Query\Expression; use Pterodactyl\Contracts\Repository\RepositoryInterface; use Pterodactyl\Exceptions\Model\DataValidationException; diff --git a/app/Repositories/Eloquent/NodeRepository.php b/app/Repositories/Eloquent/NodeRepository.php index 9a7067266..c1dd68a8a 100644 --- a/app/Repositories/Eloquent/NodeRepository.php +++ b/app/Repositories/Eloquent/NodeRepository.php @@ -18,6 +18,9 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa { use Searchable; + const THRESHOLD_PERCENTAGE_LOW = 75; + const THRESHOLD_PERCENTAGE_MEDIUM = 90; + /** * {@inheritdoc} */ @@ -56,7 +59,7 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa 'value' => number_format($value), 'max' => number_format($maxUsage), 'percent' => $percent, - 'css' => ($percent <= 75) ? 'green' : (($percent > 90) ? 'red' : 'yellow'), + 'css' => ($percent <= self::THRESHOLD_PERCENTAGE_LOW) ? 'green' : (($percent > self::THRESHOLD_PERCENTAGE_MEDIUM) ? 'red' : 'yellow'), ], ]; }) @@ -104,8 +107,7 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa $instance->setRelation( 'allocations', - $instance->allocations()->orderBy('ip', 'asc')->orderBy('port', 'asc') - ->with('server')->paginate(50) + $instance->allocations()->orderBy('ip', 'asc')->orderBy('port', 'asc')->with('server')->paginate(50) ); return $instance; diff --git a/app/Repositories/Eloquent/ServerRepository.php b/app/Repositories/Eloquent/ServerRepository.php index d66b68132..b8f5cc6fc 100644 --- a/app/Repositories/Eloquent/ServerRepository.php +++ b/app/Repositories/Eloquent/ServerRepository.php @@ -187,21 +187,27 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt } /** - * {@inheritdoc} + * Get data for use when updating a server on the Daemon. Returns an array of + * the egg and pack UUID which are used for build and rebuild. Only loads relations + * if they are missing, or refresh is set to true. + * + * @param \Pterodactyl\Models\Server $server + * @param bool $refresh + * @return array */ - public function getDaemonServiceData($id) + public function getDaemonServiceData(Server $server, bool $refresh = false): array { - Assert::integerish($id, 'First argument passed to getDaemonServiceData must be integer, received %s.'); + if (! $server->relationLoaded('egg') || $refresh) { + $server->load('egg'); + } - $instance = $this->getBuilder()->with('egg.nest', 'pack')->find($id, $this->getColumns()); - if (! $instance) { - throw new RecordNotFoundException(); + if (! $server->relationLoaded('pack') || $refresh) { + $server->load('pack'); } return [ - 'type' => $instance->egg->nest->folder, - 'option' => $instance->egg->tag, - 'pack' => (! is_null($instance->pack_id)) ? $instance->pack->uuid : null, + 'egg' => $server->getRelation('egg')->uuid, + 'pack' => is_null($server->getRelation('pack')) ? null : $server->getRelation('pack')->uuid, ]; } diff --git a/app/Repositories/Repository.php b/app/Repositories/Repository.php index f74b519fe..f9164d284 100644 --- a/app/Repositories/Repository.php +++ b/app/Repositories/Repository.php @@ -7,7 +7,7 @@ * https://opensource.org/licenses/MIT */ -namespace Pterodactyl\Repository; +namespace Pterodactyl\Repositories; use Illuminate\Foundation\Application; use Pterodactyl\Contracts\Repository\RepositoryInterface; diff --git a/app/Services/Schedules/Tasks/TaskCreationService.php b/app/Services/Schedules/Tasks/TaskCreationService.php index 0a015299d..9ea1c1783 100644 --- a/app/Services/Schedules/Tasks/TaskCreationService.php +++ b/app/Services/Schedules/Tasks/TaskCreationService.php @@ -16,6 +16,8 @@ use Pterodactyl\Exceptions\Service\Schedule\Task\TaskIntervalTooLongException; class TaskCreationService { + const MAX_INTERVAL_TIME_SECONDS = 900; + /** * @var \Pterodactyl\Contracts\Repository\TaskRepositoryInterface */ @@ -50,7 +52,7 @@ class TaskCreationService $schedule = ($schedule instanceof Schedule) ? $schedule->id : $schedule; $delay = $data['time_interval'] === 'm' ? $data['time_value'] * 60 : $data['time_value']; - if ($delay > 900) { + if ($delay > self::MAX_INTERVAL_TIME_SECONDS) { throw new TaskIntervalTooLongException(trans('exceptions.tasks.chain_interval_too_long')); } diff --git a/app/Services/Servers/StartupModificationService.php b/app/Services/Servers/StartupModificationService.php index ae91fb3ba..969d4310d 100644 --- a/app/Services/Servers/StartupModificationService.php +++ b/app/Services/Servers/StartupModificationService.php @@ -99,14 +99,17 @@ class StartupModificationService }); } - $daemonData = ['build' => [ - 'env|overwrite' => $this->environmentService->handle($server), - ]]; - + $daemonData = []; if ($this->isUserLevel(User::USER_LEVEL_ADMIN)) { $this->updateAdministrativeSettings($data, $server, $daemonData); } + $daemonData = array_merge_recursive($daemonData, [ + 'build' => [ + 'env|overwrite' => $this->environmentService->handle($server), + ], + ]); + try { $this->daemonServerRepository->setNode($server->node_id)->setAccessServer($server->uuid)->update($daemonData); } catch (RequestException $exception) { @@ -136,17 +139,15 @@ class StartupModificationService 'egg_id' => array_get($data, 'egg_id', $server->egg_id), 'pack_id' => array_get($data, 'pack_id', $server->pack_id) > 0 ? array_get($data, 'pack_id', $server->pack_id) : null, 'skip_scripts' => isset($data['skip_scripts']), + 'image' => array_get($data, 'docker_image', $server->image), ]); - if ( - $server->nest_id != array_get($data, 'nest_id', $server->nest_id) || - $server->egg_id != array_get($data, 'egg_id', $server->egg_id) || - $server->pack_id != array_get($data, 'pack_id', $server->pack_id) - ) { - $daemonData['service'] = array_merge( - $this->repository->withColumns(['id', 'egg_id', 'pack_id'])->getDaemonServiceData($server->id), + $daemonData = array_merge($daemonData, [ + 'build' => ['image' => $server->image], + 'service' => array_merge( + $this->repository->getDaemonServiceData($server, true), ['skip_scripts' => isset($data['skip_scripts'])] - ); - } + ), + ]); } } diff --git a/app/Services/Users/ToggleTwoFactorService.php b/app/Services/Users/ToggleTwoFactorService.php index 56ec6953a..e03a76389 100644 --- a/app/Services/Users/ToggleTwoFactorService.php +++ b/app/Services/Users/ToggleTwoFactorService.php @@ -1,66 +1,82 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Users; +use Carbon\Carbon; use Pterodactyl\Models\User; -use PragmaRX\Google2FA\Contracts\Google2FA; +use PragmaRX\Google2FA\Google2FA; +use Illuminate\Contracts\Config\Repository; +use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; use Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid; class ToggleTwoFactorService { /** - * @var \PragmaRX\Google2FA\Contracts\Google2FA + * @var \Illuminate\Contracts\Config\Repository */ - protected $google2FA; + private $config; + + /** + * @var \Illuminate\Contracts\Encryption\Encrypter + */ + private $encrypter; + + /** + * @var \PragmaRX\Google2FA\Google2FA + */ + private $google2FA; /** * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface */ - protected $repository; + private $repository; /** * ToggleTwoFactorService constructor. * - * @param \PragmaRX\Google2FA\Contracts\Google2FA $google2FA + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @param \PragmaRX\Google2FA\Google2FA $google2FA + * @param \Illuminate\Contracts\Config\Repository $config * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository */ public function __construct( + Encrypter $encrypter, Google2FA $google2FA, + Repository $config, UserRepositoryInterface $repository ) { + $this->config = $config; + $this->encrypter = $encrypter; $this->google2FA = $google2FA; $this->repository = $repository; } /** - * @param int|\Pterodactyl\Models\User $user - * @param string $token - * @param null|bool $toggleState + * Toggle 2FA on an account only if the token provided is valid. + * + * @param \Pterodactyl\Models\User $user + * @param string $token + * @param bool|null $toggleState * @return bool * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid */ - public function handle($user, $token, $toggleState = null) + public function handle(User $user, string $token, bool $toggleState = null): bool { - if (! $user instanceof User) { - $user = $this->repository->find($user); - } + $window = $this->config->get('pterodactyl.auth.2fa.window'); + $secret = $this->encrypter->decrypt($user->totp_secret); - if (! $this->google2FA->verifyKey($user->totp_secret, $token, 2)) { + $isValidToken = $this->google2FA->verifyKey($secret, $token, $window); + + if (! $isValidToken) { throw new TwoFactorAuthenticationTokenInvalid; } $this->repository->withoutFresh()->update($user->id, [ + 'totp_authenticated_at' => Carbon::now(), 'use_totp' => (is_null($toggleState) ? ! $user->use_totp : $toggleState), ]); diff --git a/app/Services/Users/TwoFactorSetupService.php b/app/Services/Users/TwoFactorSetupService.php index 608a3643a..a8554ccfc 100644 --- a/app/Services/Users/TwoFactorSetupService.php +++ b/app/Services/Users/TwoFactorSetupService.php @@ -10,7 +10,8 @@ namespace Pterodactyl\Services\Users; use Pterodactyl\Models\User; -use PragmaRX\Google2FA\Contracts\Google2FA; +use PragmaRX\Google2FA\Google2FA; +use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; use Illuminate\Contracts\Config\Repository as ConfigRepository; @@ -19,58 +20,62 @@ class TwoFactorSetupService /** * @var \Illuminate\Contracts\Config\Repository */ - protected $config; + private $config; /** - * @var \PragmaRX\Google2FA\Contracts\Google2FA + * @var \Illuminate\Contracts\Encryption\Encrypter */ - protected $google2FA; + private $encrypter; + + /** + * @var \PragmaRX\Google2FA\Google2FA + */ + private $google2FA; /** * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface */ - protected $repository; + private $repository; /** * TwoFactorSetupService constructor. * * @param \Illuminate\Contracts\Config\Repository $config - * @param \PragmaRX\Google2FA\Contracts\Google2FA $google2FA + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + * @param \PragmaRX\Google2FA\Google2FA $google2FA * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository */ public function __construct( ConfigRepository $config, + Encrypter $encrypter, Google2FA $google2FA, UserRepositoryInterface $repository ) { $this->config = $config; + $this->encrypter = $encrypter; $this->google2FA = $google2FA; $this->repository = $repository; } /** - * Generate a 2FA token and store it in the database. + * Generate a 2FA token and store it in the database before returning the + * QR code image. * - * @param int|\Pterodactyl\Models\User $user - * @return array + * @param \Pterodactyl\Models\User $user + * @return string * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function handle($user) + public function handle(User $user): string { - if (! $user instanceof User) { - $user = $this->repository->find($user); - } - - $secret = $this->google2FA->generateSecretKey(); + $secret = $this->google2FA->generateSecretKey($this->config->get('pterodactyl.auth.2fa.bytes')); $image = $this->google2FA->getQRCodeGoogleUrl($this->config->get('app.name'), $user->email, $secret); - $this->repository->withoutFresh()->update($user->id, ['totp_secret' => $secret]); + $this->repository->withoutFresh()->update($user->id, [ + 'totp_secret' => $this->encrypter->encrypt($secret), + ]); - return [ - 'qrImage' => $image, - 'secret' => $secret, - ]; + return $image; } } diff --git a/app/Transformers/Admin/SubuserTransformer.php b/app/Transformers/Admin/SubuserTransformer.php index 0bc0ed01a..93ed25d52 100644 --- a/app/Transformers/Admin/SubuserTransformer.php +++ b/app/Transformers/Admin/SubuserTransformer.php @@ -11,7 +11,6 @@ namespace Pterodactyl\Transformers\Admin; use Illuminate\Http\Request; use Pterodactyl\Models\Subuser; -use Pterodactyl\Models\Permission; use League\Fractal\TransformerAbstract; class SubuserTransformer extends TransformerAbstract diff --git a/app/Transformers/User/SubuserTransformer.php b/app/Transformers/User/SubuserTransformer.php index 48d9b5ceb..faac5965c 100644 --- a/app/Transformers/User/SubuserTransformer.php +++ b/app/Transformers/User/SubuserTransformer.php @@ -10,7 +10,6 @@ namespace Pterodactyl\Transformers\User; use Pterodactyl\Models\Subuser; -use Pterodactyl\Models\Permission; use League\Fractal\TransformerAbstract; class SubuserTransformer extends TransformerAbstract diff --git a/composer.json b/composer.json index 58903551f..fabe01f0f 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "mtdowling/cron-expression": "^1.2", "nesbot/carbon": "^1.22", "nicolaslopezj/searchable": "^1.9", - "pragmarx/google2fa": "^1.0", + "pragmarx/google2fa": "^2.0", "predis/predis": "^1.1", "prologue/alerts": "^0.4", "ramsey/uuid": "^3.7", @@ -46,7 +46,7 @@ "require-dev": { "barryvdh/laravel-debugbar": "^2.4", "barryvdh/laravel-ide-helper": "^2.4", - "friendsofphp/php-cs-fixer": "^2.4", + "friendsofphp/php-cs-fixer": "^2.8.0", "fzaninotto/faker": "^1.6", "mockery/mockery": "^0.9", "php-mock/php-mock-phpunit": "^1.1", diff --git a/composer.lock b/composer.lock index 2979fad37..9895b8330 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "3758867d4fb2d20e4b4e45b7c410f79b", + "content-hash": "a393763d136e25a93fd5b636229496cf", "packages": [ { "name": "appstract/laravel-blade-directives", @@ -61,16 +61,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.36.37", + "version": "3.38.1", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "a6d7fd9f32c63d018a6603a36174b4cb971fccd9" + "reference": "9f704274f4748d2039a16d45b3388ed8dde74e89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a6d7fd9f32c63d018a6603a36174b4cb971fccd9", - "reference": "a6d7fd9f32c63d018a6603a36174b4cb971fccd9", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/9f704274f4748d2039a16d45b3388ed8dde74e89", + "reference": "9f704274f4748d2039a16d45b3388ed8dde74e89", "shasum": "" }, "require": { @@ -137,61 +137,7 @@ "s3", "sdk" ], - "time": "2017-11-03T16:39:35+00:00" - }, - { - "name": "christian-riesen/base32", - "version": "1.3.1", - "source": { - "type": "git", - "url": "https://github.com/ChristianRiesen/base32.git", - "reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ChristianRiesen/base32/zipball/0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa", - "reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "4.*", - "satooshi/php-coveralls": "0.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Base32\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Riesen", - "email": "chris.riesen@gmail.com", - "homepage": "http://christianriesen.com", - "role": "Developer" - } - ], - "description": "Base32 encoder/decoder according to RFC 4648", - "homepage": "https://github.com/ChristianRiesen/base32", - "keywords": [ - "base32", - "decode", - "encode", - "rfc4648" - ], - "time": "2016-05-05T11:49:03+00:00" + "time": "2017-11-09T19:15:59+00:00" }, { "name": "daneeveritt/login-notifications", @@ -2055,6 +2001,68 @@ ], "time": "2017-11-04T11:48:34+00:00" }, + { + "name": "paragonie/constant_time_encoding", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "9e7d88e6e4015c2f06a3fa22f06e1d5faa77e6c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/9e7d88e6e4015c2f06a3fa22f06e1d5faa77e6c4", + "reference": "9e7d88e6e4015c2f06a3fa22f06e1d5faa77e6c4", + "shasum": "" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "^6", + "vimeo/psalm": "^0.3|^1" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "time": "2017-09-22T14:55:37+00:00" + }, { "name": "paragonie/random_compat", "version": "v2.0.11", @@ -2105,26 +2113,28 @@ }, { "name": "pragmarx/google2fa", - "version": "v1.0.1", + "version": "v2.0.6", "source": { "type": "git", "url": "https://github.com/antonioribeiro/google2fa.git", - "reference": "b346dc138339b745c5831405d00cff7c1351aa0d" + "reference": "bc2d654305e4d09254125f8cd390a7fbc4742d46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/b346dc138339b745c5831405d00cff7c1351aa0d", - "reference": "b346dc138339b745c5831405d00cff7c1351aa0d", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/bc2d654305e4d09254125f8cd390a7fbc4742d46", + "reference": "bc2d654305e4d09254125f8cd390a7fbc4742d46", "shasum": "" }, "require": { - "christian-riesen/base32": "~1.3", + "paragonie/constant_time_encoding": "~1.0|~2.0", "paragonie/random_compat": "~1.4|~2.0", "php": ">=5.4", "symfony/polyfill-php56": "~1.2" }, "require-dev": { - "phpspec/phpspec": "~2.1" + "bacon/bacon-qr-code": "~1.0", + "phpspec/phpspec": "~2.1", + "phpunit/phpunit": "~4" }, "suggest": { "bacon/bacon-qr-code": "Required to generate inline QR Codes." @@ -2132,11 +2142,8 @@ "type": "library", "extra": { "component": "package", - "frameworks": [ - "Laravel" - ], "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -2157,12 +2164,13 @@ ], "description": "A One Time Password Authentication package, compatible with Google Authenticator.", "keywords": [ + "2fa", "Authentication", "Two Factor Authentication", "google2fa", "laravel" ], - "time": "2016-07-18T20:25:04+00:00" + "time": "2017-09-12T06:55:05+00:00" }, { "name": "predis/predis", @@ -3796,16 +3804,16 @@ }, { "name": "watson/validating", - "version": "3.1.1", + "version": "3.1.2", "source": { "type": "git", "url": "https://github.com/dwightwatson/validating.git", - "reference": "ade13078bf2e820e244603446114a28eda51b08c" + "reference": "22edd06d45893f5d4f79c9e901bd7fbce174a79f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dwightwatson/validating/zipball/ade13078bf2e820e244603446114a28eda51b08c", - "reference": "ade13078bf2e820e244603446114a28eda51b08c", + "url": "https://api.github.com/repos/dwightwatson/validating/zipball/22edd06d45893f5d4f79c9e901bd7fbce174a79f", + "reference": "22edd06d45893f5d4f79c9e901bd7fbce174a79f", "shasum": "" }, "require": { @@ -3842,7 +3850,7 @@ "laravel", "validation" ], - "time": "2017-10-08T22:42:01+00:00" + "time": "2017-11-06T21:35:49+00:00" }, { "name": "webmozart/assert", @@ -4291,16 +4299,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.8.0", + "version": "v2.8.1", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "89e7b083f27241e03dd776cb8d6781c77e341db6" + "reference": "04f71e56e03ba2627e345e8c949c80dcef0e683e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/89e7b083f27241e03dd776cb8d6781c77e341db6", - "reference": "89e7b083f27241e03dd776cb8d6781c77e341db6", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/04f71e56e03ba2627e345e8c949c80dcef0e683e", + "reference": "04f71e56e03ba2627e345e8c949c80dcef0e683e", "shasum": "" }, "require": { @@ -4367,7 +4375,7 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2017-11-03T02:21:46+00:00" + "time": "2017-11-09T13:31:39+00:00" }, { "name": "fzaninotto/faker", @@ -4421,23 +4429,23 @@ }, { "name": "gecko-packages/gecko-php-unit", - "version": "v2.2", + "version": "v3.0", "source": { "type": "git", "url": "https://github.com/GeckoPackages/GeckoPHPUnit.git", - "reference": "ab525fac9a9ffea219687f261b02008b18ebf2d1" + "reference": "6a866551dffc2154c1b091bae3a7877d39c25ca3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GeckoPackages/GeckoPHPUnit/zipball/ab525fac9a9ffea219687f261b02008b18ebf2d1", - "reference": "ab525fac9a9ffea219687f261b02008b18ebf2d1", + "url": "https://api.github.com/repos/GeckoPackages/GeckoPHPUnit/zipball/6a866551dffc2154c1b091bae3a7877d39c25ca3", + "reference": "6a866551dffc2154c1b091bae3a7877d39c25ca3", "shasum": "" }, "require": { - "php": "^5.3.6 || ^7.0" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.4.3" + "phpunit/phpunit": "^6.0" }, "suggest": { "ext-dom": "When testing with xml.", @@ -4445,6 +4453,11 @@ "phpunit/phpunit": "This is an extension for it so make sure you have it some way." }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, "autoload": { "psr-4": { "GeckoPackages\\PHPUnit\\": "src/PHPUnit" @@ -4461,7 +4474,7 @@ "filesystem", "phpunit" ], - "time": "2017-08-23T07:39:54+00:00" + "time": "2017-08-23T07:46:41+00:00" }, { "name": "hamcrest/hamcrest-php", diff --git a/config/app.php b/config/app.php index c193e42e8..2f9da6704 100644 --- a/config/app.php +++ b/config/app.php @@ -171,7 +171,6 @@ return [ /* * Additional Dependencies */ - PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider::class, igaster\laravelTheme\themeServiceProvider::class, Prologue\Alerts\AlertsServiceProvider::class, Krucas\Settings\Providers\SettingsServiceProvider::class, @@ -213,7 +212,6 @@ return [ 'File' => Illuminate\Support\Facades\File::class, 'Fractal' => Spatie\Fractal\FractalFacade::class, 'Gate' => Illuminate\Support\Facades\Gate::class, - 'Google2FA' => PragmaRX\Google2FA\Vendor\Laravel\Facade::class, 'Hash' => Illuminate\Support\Facades\Hash::class, 'Input' => Illuminate\Support\Facades\Input::class, 'Inspiring' => Illuminate\Foundation\Inspiring::class, diff --git a/config/pterodactyl.php b/config/pterodactyl.php index bd157df23..ad371bce9 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -23,6 +23,11 @@ return [ */ 'auth' => [ 'notifications' => env('LOGIN_NOTIFICATIONS', false), + '2fa' => [ + 'bytes' => 32, + 'window' => env('APP_2FA_WINDOW', 4), + 'verify_newer' => true, + ], ], /* diff --git a/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php b/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php new file mode 100644 index 000000000..53cb6526b --- /dev/null +++ b/database/migrations/2017_11_11_161922_Add2FaLastAuthorizationTimeColumn.php @@ -0,0 +1,60 @@ +text('totp_secret')->nullable()->change(); + $table->timestampTz('totp_authenticated_at')->after('totp_secret')->nullable(); + }); + + DB::transaction(function () { + DB::table('users')->get()->each(function ($user) { + if (is_null($user->totp_secret)) { + return; + } + + DB::table('users')->where('id', $user->id)->update([ + 'totp_secret' => Crypt::encrypt($user->totp_secret), + 'updated_at' => Carbon::now()->toIso8601String(), + ]); + }); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + DB::transaction(function () { + DB::table('users')->get()->each(function ($user) { + if (is_null($user->totp_secret)) { + return; + } + + DB::table('users')->where('id', $user->id)->update([ + 'totp_secret' => Crypt::decrypt($user->totp_secret), + 'updated_at' => Carbon::now()->toIso8601String(), + ]); + }); + }); + + DB::statement('ALTER TABLE users MODIFY totp_secret CHAR(16) DEFAULT NULL'); + + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('totp_authenticated_at'); + }); + } +} diff --git a/public/themes/pterodactyl/css/pterodactyl.css b/public/themes/pterodactyl/css/pterodactyl.css index 0cc55b214..a0b9da1e8 100644 --- a/public/themes/pterodactyl/css/pterodactyl.css +++ b/public/themes/pterodactyl/css/pterodactyl.css @@ -420,3 +420,7 @@ label.control-label > span.field-optional:before { content: "optional"; color: #bbbbbb; } + +.pagination > li > a, .pagination > li > span { + padding: 3px 10px !important; +} diff --git a/public/themes/pterodactyl/js/frontend/2fa-modal.js b/public/themes/pterodactyl/js/frontend/2fa-modal.js index 022ece2ff..d542b377c 100644 --- a/public/themes/pterodactyl/js/frontend/2fa-modal.js +++ b/public/themes/pterodactyl/js/frontend/2fa-modal.js @@ -42,7 +42,6 @@ var TwoFactorModal = (function () { $('#qr_image_insert').attr('src', image.src).slideDown(); }); }); - $('#2fa_secret_insert').html(data.secret); $('#open2fa').modal('show'); }).fail(function (jqXHR) { alert('An error occured while attempting to load the 2FA setup modal. Please try again.'); diff --git a/resources/lang/es/auth.php b/resources/lang/es/auth.php index 4c33b8f13..e8269476c 100644 --- a/resources/lang/es/auth.php +++ b/resources/lang/es/auth.php @@ -6,7 +6,7 @@ return [ 'authentication_required' => 'La autenticación es necesaria para continuar.', 'remember_me' => 'Recuérdame', 'sign_in' => 'Iniciar Sesión', - 'forgot_password' => 'Olvidé mi contraseña!', + 'forgot_password' => '¡Olvidé mi contraseña!', 'request_reset_text' => '¿Olvidaste tu contraseña? No es el fin del mundo, sólo proporcione su correo electrónico a continuación.', 'reset_password_text' => 'Restablece la contraseña de su cuenta.', 'reset_password' => 'Restablece contraseña de cuenta.', diff --git a/resources/lang/es/server.php b/resources/lang/es/server.php index 8bbb31b4f..b643d4216 100644 --- a/resources/lang/es/server.php +++ b/resources/lang/es/server.php @@ -259,7 +259,7 @@ return [ 'header' => 'El Administrador De Archivos', 'header_sub' => 'Administrar todos tus archivos directamente desde la web.', 'loading' => 'La carga inicial de la estructura del archivo, esto puede tardar unos segundos.', - 'path' => 'Cuando la configuración de rutas de archivo en su servidor de plugins o configuración que debe utilizar :path de acceso como base de la ruta. El tamaño máximo para la web basado en la carga de archivos a este nodo es :tamaño de la.', + 'path' => 'Cuando la configuración de rutas de archivo en su servidor de plugins o configuración que debe utilizar :path de acceso como base de la ruta. El tamaño máximo para la web basado en la carga de archivos a este nodo es :size de la.', 'seconds_ago' => 'hace segundos', 'file_name' => 'Nombre De Archivo', 'size' => 'Tamaño', diff --git a/resources/themes/pterodactyl/admin/nodes/view/allocation.blade.php b/resources/themes/pterodactyl/admin/nodes/view/allocation.blade.php index 730ed5770..50a6a84e5 100644 --- a/resources/themes/pterodactyl/admin/nodes/view/allocation.blade.php +++ b/resources/themes/pterodactyl/admin/nodes/view/allocation.blade.php @@ -72,9 +72,11 @@ @endforeach -
+ @if($node->allocations->hasPages()) + + @endifA brief description of this server.
This token should not be shared with anyone as it has full control over this server.
-Resetting this token will cause any requests using the old token to fail.
-The Docker image to use for this server. The default image for the selected egg is .
This is the name that is used throughout the panel and in emails sent to clients.
This is the default language that all clients will use unless they manually change it.
-Require your administrators or users to have 2FA enabled. Users include Admins. Everybody includes Sub Users.
+For improved security you can require all administrators to have 2-Factor authentication enabled, or even require it for all users on the Panel.
php artisan pterodactyl:mail
in this project's root folder.php artisan p:environment:mail
in this project's root folder.