Path: blob/1.0-develop/tests/Integration/Api/Client/AccountControllerTest.php
14044 views
<?php12namespace Pterodactyl\Tests\Integration\Api\Client;34use Illuminate\Support\Str;5use Pterodactyl\Models\User;6use Illuminate\Http\Response;7use Pterodactyl\Models\Subuser;8use Illuminate\Support\Facades\Bus;9use Illuminate\Support\Facades\Hash;10use Pterodactyl\Jobs\RevokeSftpAccessJob;1112class AccountControllerTest extends ClientApiIntegrationTestCase13{14/**15* Test that the user's account details are returned from the account endpoint.16*/17public function testAccountDetailsAreReturned()18{19/** @var User $user */20$user = User::factory()->create();2122$response = $this->actingAs($user)->get('/api/client/account');2324$response->assertOk()->assertJson([25'object' => 'user',26'attributes' => [27'id' => $user->id,28'admin' => false,29'username' => $user->username,30'email' => $user->email,31'first_name' => $user->name_first,32'last_name' => $user->name_last,33'language' => $user->language,34],35]);36}3738/**39* Test that the user's email address can be updated via the API.40*/41public function testEmailIsUpdated()42{43$user = User::factory()->create();4445$this->actingAs($user)46->putJson('/api/client/account/email', [47'email' => $email = Str::random() . '@example.com',48'password' => 'password',49])50->assertNoContent();5152$this->assertActivityFor('user:account.email-changed', $user, $user);53$this->assertDatabaseHas('users', ['id' => $user->id, 'email' => $email]);54}5556public function testEmailChangeIsThrottled(): void57{58$users = User::factory()->count(2)->create();59$endpoint = route('api:client.account.update-email');6061for ($i = 0; $i < 3; ++$i) {62$this->actingAs($users[0])63->putJson($endpoint, ['email' => "foo+{$i}@example.com", 'password' => 'password'])64->assertNoContent();65}6667$this68->putJson($endpoint, ['email' => '[email protected]', 'password' => 'password'])69->assertTooManyRequests();7071// The other user should still be able to update their email because the throttle72// is tied to the account, not to the IP address.73$this->actingAs($users[1])74->putJson($endpoint, ['email' => '[email protected]', 'password' => 'password'])75->assertNoContent();76}7778/**79* Tests that an email is not updated if the password provided in the request is not80* valid for the account.81*/82public function testEmailIsNotUpdatedWhenPasswordIsInvalid()83{84/** @var User $user */85$user = User::factory()->create();8687$response = $this->actingAs($user)->putJson('/api/client/account/email', [88'email' => '[email protected]',89'password' => 'invalid',90]);9192$response->assertStatus(Response::HTTP_BAD_REQUEST);93$response->assertJsonPath('errors.0.code', 'InvalidPasswordProvidedException');94$response->assertJsonPath('errors.0.detail', 'The password provided was invalid for this account.');95}9697/**98* Tests that an email is not updated if an invalid email address is passed through99* in the request.100*/101public function testEmailIsNotUpdatedWhenNotValid()102{103/** @var User $user */104$user = User::factory()->create();105106$response = $this->actingAs($user)->putJson('/api/client/account/email', [107'email' => '',108'password' => 'password',109]);110111$response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);112$response->assertJsonPath('errors.0.meta.rule', 'required');113$response->assertJsonPath('errors.0.detail', 'The email field is required.');114115$response = $this->actingAs($user)->putJson('/api/client/account/email', [116'email' => 'invalid',117'password' => 'password',118]);119120$response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);121$response->assertJsonPath('errors.0.meta.rule', 'email');122$response->assertJsonPath('errors.0.detail', 'The email must be a valid email address.');123}124125/**126* Test that the password for an account can be successfully updated.127*/128public function testPasswordIsUpdated()129{130$user = User::factory()->create();131132// Assign the user to two servers, one as the owner the other as a subuser, both133// on different nodes to ensure our logic fires off correctly and the user has their134// credentials revoked on both nodes.135$server = $this->createServerModel(['owner_id' => $user->id]);136$server2 = $this->createServerModel();137Subuser::factory()->for($server2)->for($user)->create();138139$initialHash = $user->password;140141Bus::fake([RevokeSftpAccessJob::class]);142143$this->actingAs($user)144->putJson('/api/client/account/password', [145'current_password' => 'password',146'password' => 'New_Password1',147'password_confirmation' => 'New_Password1',148])149->assertNoContent();150151$user = $user->refresh();152153$this->assertNotEquals($user->password, $initialHash);154$this->assertTrue(Hash::check('New_Password1', $user->password));155$this->assertFalse(Hash::check('password', $user->password));156157$this->assertActivityFor('user:account.password-changed', $user, $user);158$this->assertNotEquals($server->node_id, $server2->node_id);159160Bus::assertDispatchedTimes(RevokeSftpAccessJob::class, 2);161Bus::assertDispatched(fn (RevokeSftpAccessJob $job) => $job->user === $user->uuid && $job->target->is($server->node));162Bus::assertDispatched(fn (RevokeSftpAccessJob $job) => $job->user === $user->uuid && $job->target->is($server2->node));163}164165/**166* Test that the password for an account is not updated if the current password is not167* provided correctly.168*/169public function testPasswordIsNotUpdatedIfCurrentPasswordIsInvalid()170{171/** @var User $user */172$user = User::factory()->create();173174$response = $this->actingAs($user)->putJson('/api/client/account/password', [175'current_password' => 'invalid',176'password' => 'New_Password1',177'password_confirmation' => 'New_Password1',178]);179180$response->assertStatus(Response::HTTP_BAD_REQUEST);181$response->assertJsonPath('errors.0.code', 'InvalidPasswordProvidedException');182$response->assertJsonPath('errors.0.detail', 'The password provided was invalid for this account.');183}184185/**186* Test that a validation error is returned to the user if no password is provided or if187* the password is below the minimum password length.188*/189public function testErrorIsReturnedForInvalidRequestData()190{191$user = User::factory()->create();192193$this->actingAs($user)->putJson('/api/client/account/password', [194'current_password' => 'password',195])196->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)197->assertJsonPath('errors.0.meta.rule', 'required');198199$this->actingAs($user)->putJson('/api/client/account/password', [200'current_password' => 'password',201'password' => 'pass',202'password_confirmation' => 'pass',203])204->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)205->assertJsonPath('errors.0.meta.rule', 'min');206}207208/**209* Test that a validation error is returned if the password passed in the request210* does not have a confirmation, or the confirmation is not the same as the password.211*/212public function testErrorIsReturnedIfPasswordIsNotConfirmed()213{214/** @var User $user */215$user = User::factory()->create();216217$response = $this->actingAs($user)->putJson('/api/client/account/password', [218'current_password' => 'password',219'password' => 'New_Password1',220'password_confirmation' => 'Invalid_New_Password',221]);222223$response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);224$response->assertJsonPath('errors.0.meta.rule', 'confirmed');225$response->assertJsonPath('errors.0.detail', 'The password confirmation does not match.');226}227}228229230