Skip to content

Commit 325cbbb

Browse files
2000Arionmatthewpidanny6167Red-Banana-OfficialFallen-Breath
authored
Panel 1.14.1 (#10)
* Update CHANGELOG.md Signed-off-by: Matthew Penner <me@matthewp.io> * chore: update laravel 11.28.1 -> 11.31.0 Signed-off-by: Matthew Penner <me@matthewp.io> * Update CHANGELOG.md Signed-off-by: Matthew Penner <me@matthewp.io> * nix: update flake.lock Flake lock file updates: • Updated input 'flake-parts': 'github:hercules-ci/flake-parts/bcef6817a8b2aa20a5a6dbb19b43e63c5bf8619a?narHash=sha256-HO4zgY0ekfwO5bX0QH/3kJ/h4KvUDFZg8YpkNwIbg1U%3D' (2024-09-12) → 'github:hercules-ci/flake-parts/b905f6fc23a9051a6e1b741e1438dbfc0634c6de?narHash=sha256-%2Bhu54pAoLDEZT9pjHlqL9DNzWz0NbUn8NEAHP7PQPzU%3D' (2025-01-06) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/99dc8785f6a0adac95f5e2ab05cc2e1bf666d172?narHash=sha256-gI9kkaH0ZjakJOKrdjaI/VbaMEo9qBbSUl93DnU7f4c%3D' (2024-09-16) → 'github:NixOS/nixpkgs/130595eba61081acde9001f43de3248d8888ac4a?narHash=sha256-Xb8mke6UCYjge9kPR9o4P1nVrhk7QBbKv3xQ9cj7h2s%3D' (2025-01-10) * fix: db schema dump not importing on older MariaDB versions Signed-off-by: Matthew Penner <me@matthewp.io> * nix: update flake.lock Flake lock file updates: • Updated input 'flake-parts': 'github:hercules-ci/flake-parts/b905f6fc23a9051a6e1b741e1438dbfc0634c6de?narHash=sha256-%2Bhu54pAoLDEZT9pjHlqL9DNzWz0NbUn8NEAHP7PQPzU%3D' (2025-01-06) → 'github:hercules-ci/flake-parts/32ea77a06711b758da0ad9bd6a844c5740a87abd?narHash=sha256-7H9XgNiGLKN1G1CgRh0vUL4AheZSYzPm%2BzmZ7vxbJdo%3D' (2025-02-01) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/130595eba61081acde9001f43de3248d8888ac4a?narHash=sha256-Xb8mke6UCYjge9kPR9o4P1nVrhk7QBbKv3xQ9cj7h2s%3D' (2025-01-10) → 'github:NixOS/nixpkgs/2ff53fe64443980e139eaa286017f53f88336dd0?narHash=sha256-%2B/bYK3DbPxMIvSL4zArkMX0LQvS7rzBKXnDXLfKyRVc%3D' (2025-02-13) * fix: EnvironmentWriterTrait not allowing null values Fixes pterodactyl#5108 Signed-off-by: Matthew Penner <me@matthewp.io> * nix: add `yarn` to devShell Signed-off-by: Matthew Penner <me@matthewp.io> * chore: allow overriding Laravel storage path using `$APP_STORAGE_PATH` Signed-off-by: Matthew Penner <me@matthewp.io> * chore: update link to additional eggs in README (pterodactyl#5316) * eggs: update Ark Survival Evolved (pterodactyl#5361) * ci: switch to `ubuntu-24.04` runner Signed-off-by: Matthew Penner <me@matthewp.io> * docker: switch to PHP 8.3 (pterodactyl#5173) * fix: add additional input validation Signed-off-by: Matthew Penner <me@matthewp.io> * fix: timezone issue in `Schedule::getNextRunDate` (pterodactyl#5381) * Update PHP and JS dependencies to latest versions and modernize codebase (pterodactyl#5446) * Update FUNDING.yml * Fix sponsor link in README.md * Load `IBM Plex Sans` from code and export as part of bundle (pterodactyl#5447) Removes the need to load any external resources for fonts. Resolves pterodactyl#5343 * Don't delete the initial content when using "Ctrl+Z" to undo (pterodactyl#5448) Resolves pterodactyl#5263 * Update build workflows (pterodactyl#5449) * FIX: Renamed 'batch_uuid' field to 'batch' in ActivityLogService (pterodactyl#5396) The field was renamed to match the column name in the database and to maintain consistency across the codebase. * Return correct error message when deleting self, add test coverage (pterodactyl#5456) * Ensure that a node description can be set, add additional test coverage (pterodactyl#5457) * Don't be strict about upload size, so long as it is a positive integer (pterodactyl#5458) * Support zero-byte file uploads (pterodactyl#5459) * Use local certs for webpack serve (pterodactyl#5460) * Fix admin server manage boxes misalignment when suspending a server (pterodactyl#5461) * don't endlessly poll websocket when disconnected due to suspension (pterodactyl#5464) * Fix missing `evt` parameter in websocket logic (pterodactyl#5470) * Disconnect SFTP/Websocket when a user is removed as a subuser (pterodactyl#5472) * Don't render raw HTML returned by the alert bag (pterodactyl#5475) ref: GHSA-mgr9-6c2j-jxrq * Ensure that TOTP tokens cannot be reused (pterodactyl#5481) * Update contact email for reporting vulnerabilities (pterodactyl#5489) * Cleanup issues template (pterodactyl#5490) * Cast old() returned value to an Integer (pterodactyl#5163) * Feature: Sort Users on Admin/Users by Administrators first (pterodactyl#5098) * Update Docker container for Forge installation script (pterodactyl#5424) * Update CHANGELOG.md (pterodactyl#5502) * Clear allocation notes on server deletion (pterodactyl#5157) * update release.yaml for testing * Merge commit from fork * Add throttling to resource creation endpoints * Fix middleware registration for the throttlers * Lock the server's resource models when adding new ones * Throttle subusers even more --------- Co-authored-by: DaneEveritt <dane@daneeveritt.com> * Don't include `v` in the version identifier * Cast presigned URL lifespan to integer (pterodactyl#5515) * Update node/view/servers title to be consistent (pterodactyl#5514) * Remove <strong> tags in admin area notifications (pterodactyl#5520) * feat: Auth Required modal for hytale (pterodactyl#5526) This PR add modal related to requiring auth to download or update hytale server. Egg feature to use: `hytale_oauth` Preview: https://github.com/user-attachments/assets/77bd4f16-ba5b-4652-88b1-7abfd4ab45b8 * [🫸] Sync gh with prod New version branch: panel-1.12.0 created after panel update * Update i18n to resolve issues rendering unclosed div tags (pterodactyl#5547) * Added support for viewing and deleting of any Application API Keys (pterodactyl#5176) This PR closes Issue pterodactyl#5175. For context, I am aware that Application API Keys are deprecated in favour of Client API Keys however they are still operational within Pterodactyl and thus, not fully removed. Currently in Pterodactyl, as an Admin, you can only view your Application API Keys on the Admin Panel. In this PR, I made it so all Application Keys are visible and deletable. The key strings are obfuscated if the key does not belong to the user viewing. ![image](https://github.com/user-attachments/assets/9edb2533-d99c-4ec3-80cc-25630fd48594) The reason for adding this is primarily so other admin users can be aware of and delete another admin user's Application API keys from the UI. This functionality is useful in the event of a malicious user compromising an admin account, creating some API Keys to continue their attacks and the owner of the compromised admin account being unaware of Application API Keys. In this instance, even after a password reset, the attack could continue via the Application API without the admin realising it. I've tested the creation and deleting of keys along with using keys via the Application API to ensure no breakages have occurred. --------- Co-authored-by: DaneEveritt <dane@daneeveritt.com> * Scope the remote node token to limit the servers it can manage for backups/transfers (pterodactyl#5476) Improves the security posture of things more by limiting the servers that a node can even communicate about with the Panel. * Fix Unicode character width rendering using Unicode11Addon in xterm.js [TO FIX pterodactyl#2484] (pterodactyl#5524) fixes pterodactyl#2484 Co-authored-by: cesarmr-github <167689141+cesarmr-github@users.noreply.github.com> * Reset page on showOnlyAdmin change (pterodactyl#5559) closes pterodactyl#5557 * Improve SFTP session revocation to cover password changes and account deletion (pterodactyl#5568) This expands upon previous work done to better disconnect users from SFTP when different events occur within Pterodactyl. This new logic also accounts for password changes and their account being deleted entirely from the system. These events now trigger background jobs that will reach out to every node they are associated with to ensure they're disconnected if currently connected. * Improve security posture, update dependencies (pterodactyl#5569) Very minor changes, just adding some default headers in the event people don't configure this on their webserver. Also updating some packages to resolve open security alerts. * Add support for stripe-style identifiers on existing models with UUIDs (pterodactyl#5548) This is a partial implementation to begin moving towards stripe-style identifiers for resources in the system. Any models with an existing `uuid` column can easily be updated to return an identifier in the format of `prfx_xyz` where `prfx` is a four character prefix, and `xyz` is the UUID, encoded using base-32. These are quite easy to use within the API layer because we just need to do one quick transformation to extract the UUID for those models. This PR implements that logic for servers in the `SubstituteClientBindings` logic. A future PR will need to come through and handle identifiers for models that _don't_ currently use UUIDs for reference that we want to expose to clients. In those cases it is easier to just generate base-32 encoded UUID7s that get stored in the database and indexed. They follow the same base approach, but you don't need to do any transformations in the code (other than stripping the prefix, unless we decide to store the prefix). There is also now a `PTERODACTYL_USE_SERVER_IDENTIFIERS` environment variable, that when set to true, updates the front-end and API response to use this new identifier in place of the `uuidShort` value. * Remove deprecated version in compose file (pterodactyl#5498) This minor change removes the deprecated `version` property from the Docker Compose file to avoid the warning when running it, aligning it with current best practices and recommendations. * Replace an old reference to quay.io (pterodactyl#5210) * bump rate limits until we can improve some bad endpoint calls * Update CHANGELOG.md * Sync gh with prod * Sync gh with prod Updated panel to 1.12.1 * Fix: Revert changes to RunTaskJob traits to fix scheduled tasks (pterodactyl#5607) This change reverts changes that were made to the traits (direct and inherited) to RunTaskJob in https://github.com/pterodactyl/panel/pull/5568/changes#diff-772e8e2cf1167529b09210b934e9c0ec4fe12d99c35317f293d504aaee5cb95c Closes pterodactyl#5600 * Fix: Enable Select2 dropdowns inside modals on Transfer Server (Search functionality now working) (pterodactyl#5590) closes pterodactyl#5588 * Fix: Compare to correct variable in startup variable activity log (pterodactyl#5605) - Fixes issue where the panel would create activity logs even when the value didn't change - Log an empty string instead of displaying "null" when the variable is empty Closes pterodactyl#5604 * Fix log permissions, HASHIDS env var generation, and the two errors that have been showing up since the v1.12.0 update (pterodactyl#5324) * Fix transfer status permission checks (pterodactyl#5573) * Update CHANGELOG.md * Sync gh with prod Create new panel-1.12.1 branch for upcoming update * Sync gh with prod * Do not include issue templates * Update app version * Sync gh with prod (1/2) * Sync gh with prod (2/2) * Sync gh with prod * Sync gh with prod Updated to v1.12.3 * Update MaintenanceContainer.tsx * Panel 1.14.0 * Update to v1.14.1 --------- Signed-off-by: Matthew Penner <me@matthewp.io> Co-authored-by: Matthew Penner <me@matthewp.io> Co-authored-by: Daniel Barton <danielb@purpleflaghosting.com> Co-authored-by: Red Banana <108609991+Red-Banana-Official@users.noreply.github.com> Co-authored-by: Fallen_Breath <nellaforax@outlook.com> Co-authored-by: Dane Everitt <dane@daneeveritt.com> Co-authored-by: Anthony <anthony@physgun.com> Co-authored-by: Sergey Serpichenko <fongolcs@gmail.com> Co-authored-by: cesarmr-github <167689141+cesarmr-github@users.noreply.github.com> Co-authored-by: Mackenzie Molloy <48184419+MackenzieMolloy@users.noreply.github.com> Co-authored-by: Alan Escarcha <alanescarcha11@gmail.com> Co-authored-by: Gio <34938366+giomxx@users.noreply.github.com> Co-authored-by: Всеволод Мельник <89664351+vsevolodmelnyk@users.noreply.github.com> Co-authored-by: Sam Schumacher <38103916+HerrSammyDE@users.noreply.github.com> Co-authored-by: AndyIsHereBoi <69745145+AndyIsHereBoi@users.noreply.github.com> Co-authored-by: Dawid Jaworski <contact@minerpl.xyz> Co-authored-by: VoidValue <167689141+voidvalue-dev@users.noreply.github.com> Co-authored-by: _KroZen_ <109613115+KroZen-Dev@users.noreply.github.com> Co-authored-by: lajczi <lajczi@disroot.org> Co-authored-by: Loki <59907407+Loki-101@users.noreply.github.com> Co-authored-by: Carlton <28676680+MrSoulPenguin@users.noreply.github.com>
1 parent b33e3a1 commit 325cbbb

63 files changed

Lines changed: 733 additions & 272 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@ This file is a running track of new features and fixes to each version of the pa
33

44
This project follows [Semantic Versioning](http://semver.org) guidelines.
55

6+
## v1.12.3
7+
### Fixed
8+
* Adds a rate limit when changing email addresses on an account to prevent account enumeration.
9+
* Fixes backups, databases, and allocation creation to properly acquire a lock before checking if users can create more resources on a server.
10+
11+
### Changed
12+
* Added Java 25 as an option to the default Minecraft eggs.
13+
* Updates Paper install script and adds support for Java 25 to default egg.
14+
* JWTs now require at least one `JwtScope` enum value to be set when generating. Failure to provide a scope will result in an exception being raised.
15+
616
## v1.12.2
717
### Fixed
818
* Fixes task execution jobs to correctly dispatch the next job in the chain.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
namespace Pterodactyl\Console\Commands\Environment\Addons;
4+
5+
use Illuminate\Support\Str;
6+
use Illuminate\Console\Command;
7+
use Illuminate\Support\Collection;
8+
use Illuminate\Support\Facades\File;
9+
use Illuminate\Support\Facades\Process;
10+
11+
class RunHooksCommand extends Command
12+
{
13+
protected $signature = 'p:environment:addons:run-hooks
14+
{event : The lifecycle event to run hooks for (e.g. post-install).}';
15+
16+
protected $description = 'Execute addon lifecycle hook scripts for the given event.';
17+
18+
/**
19+
* Runs every executable "addons/<name>/hooks/<event>" script for the given lifecycle event when addon hooks are enabled.
20+
*/
21+
public function handle(): int
22+
{
23+
if (!config('addons.hooks_enabled')) {
24+
return self::SUCCESS;
25+
}
26+
27+
$event = $this->argument('event');
28+
if (!Str::isMatch('/^[a-z0-9-]+$/', $event)) {
29+
$this->components->error("Invalid hook event name: {$event}");
30+
31+
return self::INVALID;
32+
}
33+
34+
$hooks = Collection::make(File::glob(base_path("addons/*/hooks/{$event}")) ?: [])
35+
->filter(fn (string $hook) => is_executable($hook))
36+
->values();
37+
38+
if ($hooks->isEmpty()) {
39+
return self::SUCCESS;
40+
}
41+
42+
if ($this->input->isInteractive() && !$this->confirm(
43+
sprintf('Execute %d addon hook script(s) for the "%s" event? They run with the privileges of this process.', $hooks->count(), $event)
44+
)) {
45+
return self::SUCCESS;
46+
}
47+
48+
$hooks->each($this->runHook(...));
49+
50+
return self::SUCCESS;
51+
}
52+
53+
/**
54+
* Streams a single hook's output, reporting a non-zero exit without aborting the remaining hooks.
55+
*/
56+
private function runHook(string $hook): void
57+
{
58+
$this->components->info("Running addon hook: {$hook}");
59+
60+
$result = Process::path(base_path())
61+
->forever()
62+
->run([$hook], fn (string $type, string $output) => $this->output->write($output));
63+
64+
if ($result->failed()) {
65+
$this->components->warn("Addon hook exited with an error: {$hook}");
66+
}
67+
}
68+
}

app/Enum/JwtScope.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Pterodactyl\Enum;
4+
5+
enum JwtScope: string
6+
{
7+
case Websocket = 'websocket';
8+
case FileUpload = 'file-upload';
9+
case FileDownload = 'file-download';
10+
case BackupDownload = 'backup-download';
11+
case ServerTransfer = 'transfer';
12+
}

app/Extensions/Laravel/Sanctum/NewAccessToken.php

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,38 @@
33
namespace Pterodactyl\Extensions\Laravel\Sanctum;
44

55
use Pterodactyl\Models\ApiKey;
6-
use Laravel\Sanctum\NewAccessToken as SanctumAccessToken;
6+
use Illuminate\Contracts\Support\Jsonable;
7+
use Illuminate\Contracts\Support\Arrayable;
78

8-
/**
9-
* @property ApiKey $accessToken
10-
*/
11-
class NewAccessToken extends SanctumAccessToken
9+
class NewAccessToken implements Arrayable, Jsonable
1210
{
1311
/**
1412
* NewAccessToken constructor.
13+
*/
14+
public function __construct(public ApiKey $accessToken, public string $plainTextToken)
15+
{
16+
}
17+
18+
/**
19+
* Get the instance as an array.
20+
*
21+
* @return array<string, ApiKey|string>
22+
*/
23+
public function toArray()
24+
{
25+
return [
26+
'accessToken' => $this->accessToken,
27+
'plainTextToken' => $this->plainTextToken,
28+
];
29+
}
30+
31+
/**
32+
* Convert the object to its JSON representation.
1533
*
16-
* @noinspection PhpMissingParentConstructorInspection
34+
* @param int $options
1735
*/
18-
public function __construct(ApiKey $accessToken, string $plainTextToken)
36+
public function toJson($options = 0)
1937
{
20-
$this->accessToken = $accessToken;
21-
$this->plainTextToken = $plainTextToken;
38+
return json_encode($this->toArray(), $options);
2239
}
2340
}

app/Http/Controllers/Admin/NodeAutoDeployController.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ public function __construct(
2222
}
2323

2424
/**
25-
* Generates a new API key for the logged-in user with only permission to read
26-
* nodes, and returns that as the deployment key for a node.
25+
* Generates a new API key for the logged-in user with read and write permission
26+
* to nodes, and returns that as the deployment key for a node.
2727
*
2828
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
2929
*/
@@ -32,17 +32,17 @@ public function __invoke(Request $request, Node $node): JsonResponse
3232
$key = ApiKey::query()
3333
->where('user_id', $request->user()->id)
3434
->where('key_type', ApiKey::TYPE_APPLICATION)
35-
->where('r_nodes', 1)
35+
->where('r_nodes', 3)
3636
->first();
3737

38-
// We couldn't find a key that exists for this user with only permission for
39-
// reading nodes. Go ahead and create it now.
38+
// We couldn't find a key that exists for this user with read and write
39+
// permission for nodes. Go ahead and create it now.
4040
if (!$key) {
4141
$key = $this->keyCreationService->setKeyType(ApiKey::TYPE_APPLICATION)->handle([
4242
'user_id' => $request->user()->id,
4343
'memo' => 'Automatically generated node deployment key.',
4444
'allowed_ips' => [],
45-
], ['r_nodes' => 1]);
45+
], ['r_nodes' => 3]);
4646
}
4747

4848
return new JsonResponse([

app/Http/Controllers/Admin/Nodes/SystemInformationController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public function __invoke(Request $request, Node $node): JsonResponse
3333
'type' => Str::title($data['os'] ?? 'Unknown'),
3434
'arch' => $data['architecture'] ?? '--',
3535
'release' => $data['kernel_version'] ?? '--',
36-
'cpus' => $data['cpu_count'] ?? 0,
36+
'cpus' => (int) ($data['cpu_count'] ?? 0),
3737
],
3838
]);
3939
}

app/Http/Controllers/Admin/Servers/ServerTransferController.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Carbon\CarbonImmutable;
66
use Illuminate\Http\Request;
7+
use Pterodactyl\Enum\JwtScope;
78
use Pterodactyl\Models\Server;
89
use Illuminate\Http\RedirectResponse;
910
use Prologue\Alerts\AlertsMessageBag;
@@ -78,7 +79,8 @@ public function transfer(Request $request, Server $server): RedirectResponse
7879
$token = $this->nodeJWTService
7980
->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
8081
->setSubject($server->uuid)
81-
->handle($transfer->newNode, $server->uuid, 'sha256');
82+
->setScopes(JwtScope::ServerTransfer)
83+
->handle($transfer->newNode, $server->uuid);
8284

8385
// Notify the source node of the pending outgoing transfer.
8486
$this->daemonTransferRepository->setServer($server)->notify($transfer->newNode, $token);

app/Http/Controllers/Admin/ServersController.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use Pterodactyl\Services\Servers\DetailsModificationService;
2626
use Pterodactyl\Services\Servers\StartupModificationService;
2727
use Pterodactyl\Contracts\Repository\NestRepositoryInterface;
28+
use Pterodactyl\Http\Requests\Admin\ServerDetailsFormRequest;
2829
use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository;
2930
use Pterodactyl\Services\Databases\DatabaseManagementService;
3031
use Illuminate\Contracts\Config\Repository as ConfigRepository;
@@ -67,9 +68,9 @@ public function __construct(
6768
* @throws DataValidationException
6869
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
6970
*/
70-
public function setDetails(Request $request, Server $server): RedirectResponse
71+
public function setDetails(ServerDetailsFormRequest $request, Server $server): RedirectResponse
7172
{
72-
$this->detailsModificationService->handle($server, $request->only([
73+
$this->detailsModificationService->handle($server, $request->normalize([
7374
'owner_id', 'external_id', 'name', 'description',
7475
]));
7576

app/Http/Controllers/Api/Application/Nodes/NodeConfigurationController.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
use Pterodactyl\Models\Node;
66
use Illuminate\Http\JsonResponse;
7-
use Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodeRequest;
87
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
8+
use Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodeConfigurationRequest;
99

1010
class NodeConfigurationController extends ApplicationApiController
1111
{
@@ -14,7 +14,7 @@ class NodeConfigurationController extends ApplicationApiController
1414
* to remote machines so long as an API key is provided to the machine to make the request
1515
* with, and the node is known.
1616
*/
17-
public function __invoke(GetNodeRequest $request, Node $node): JsonResponse
17+
public function __invoke(GetNodeConfigurationRequest $request, Node $node): JsonResponse
1818
{
1919
return new JsonResponse($node->getConfiguration());
2020
}

app/Http/Controllers/Api/Client/AccountController.php

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,20 @@
77
use Illuminate\Auth\AuthManager;
88
use Illuminate\Http\JsonResponse;
99
use Pterodactyl\Facades\Activity;
10+
use Illuminate\Support\Facades\RateLimiter;
1011
use Pterodactyl\Services\Users\UserUpdateService;
1112
use Pterodactyl\Transformers\Api\Client\AccountTransformer;
1213
use Pterodactyl\Http\Requests\Api\Client\Account\UpdateEmailRequest;
1314
use Pterodactyl\Http\Requests\Api\Client\Account\UpdatePasswordRequest;
15+
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
1416

1517
class AccountController extends ClientApiController
1618
{
19+
/**
20+
* The number of seconds that must elapse before the email change throttle resets.
21+
*/
22+
private const EMAIL_UPDATE_THROTTLE = 60 * 60 * 24;
23+
1724
/**
1825
* AccountController constructor.
1926
*/
@@ -34,12 +41,22 @@ public function index(Request $request): array
3441
*/
3542
public function updateEmail(UpdateEmailRequest $request): JsonResponse
3643
{
37-
$original = $request->user()->email;
38-
$this->updateService->handle($request->user(), $request->validated());
44+
$user = $request->user();
45+
// Only allow a user to change their email three times in the span
46+
// of 24 hours. This prevents malicious users from trying to find
47+
// existing accounts in the system by constantly changing their email.
48+
if (RateLimiter::tooManyAttempts($key = "user:update-email:{$user->uuid}", 3)) {
49+
throw new TooManyRequestsHttpException(message: 'Your email address has been changed too many times today. Please try again later.');
50+
}
51+
52+
$original = $user->email;
53+
if (mb_strtolower($original) !== mb_strtolower($request->validated('email'))) {
54+
RateLimiter::hit($key, self::EMAIL_UPDATE_THROTTLE);
55+
56+
$this->updateService->handle($user, $request->validated());
3957

40-
if ($original !== $request->input('email')) {
4158
Activity::event('user:account.email-changed')
42-
->property(['old' => $original, 'new' => $request->input('email')])
59+
->property(['old' => $original, 'new' => $request->validated('email')])
4360
->log();
4461
}
4562

0 commit comments

Comments
 (0)