Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pterodactyl
GitHub Repository: pterodactyl/panel
Path: blob/1.0-develop/tests/Integration/Api/Client/AccountControllerTest.php
14044 views
1
<?php
2
3
namespace Pterodactyl\Tests\Integration\Api\Client;
4
5
use Illuminate\Support\Str;
6
use Pterodactyl\Models\User;
7
use Illuminate\Http\Response;
8
use Pterodactyl\Models\Subuser;
9
use Illuminate\Support\Facades\Bus;
10
use Illuminate\Support\Facades\Hash;
11
use Pterodactyl\Jobs\RevokeSftpAccessJob;
12
13
class AccountControllerTest extends ClientApiIntegrationTestCase
14
{
15
/**
16
* Test that the user's account details are returned from the account endpoint.
17
*/
18
public function testAccountDetailsAreReturned()
19
{
20
/** @var User $user */
21
$user = User::factory()->create();
22
23
$response = $this->actingAs($user)->get('/api/client/account');
24
25
$response->assertOk()->assertJson([
26
'object' => 'user',
27
'attributes' => [
28
'id' => $user->id,
29
'admin' => false,
30
'username' => $user->username,
31
'email' => $user->email,
32
'first_name' => $user->name_first,
33
'last_name' => $user->name_last,
34
'language' => $user->language,
35
],
36
]);
37
}
38
39
/**
40
* Test that the user's email address can be updated via the API.
41
*/
42
public function testEmailIsUpdated()
43
{
44
$user = User::factory()->create();
45
46
$this->actingAs($user)
47
->putJson('/api/client/account/email', [
48
'email' => $email = Str::random() . '@example.com',
49
'password' => 'password',
50
])
51
->assertNoContent();
52
53
$this->assertActivityFor('user:account.email-changed', $user, $user);
54
$this->assertDatabaseHas('users', ['id' => $user->id, 'email' => $email]);
55
}
56
57
public function testEmailChangeIsThrottled(): void
58
{
59
$users = User::factory()->count(2)->create();
60
$endpoint = route('api:client.account.update-email');
61
62
for ($i = 0; $i < 3; ++$i) {
63
$this->actingAs($users[0])
64
->putJson($endpoint, ['email' => "foo+{$i}@example.com", 'password' => 'password'])
65
->assertNoContent();
66
}
67
68
$this
69
->putJson($endpoint, ['email' => '[email protected]', 'password' => 'password'])
70
->assertTooManyRequests();
71
72
// The other user should still be able to update their email because the throttle
73
// is tied to the account, not to the IP address.
74
$this->actingAs($users[1])
75
->putJson($endpoint, ['email' => '[email protected]', 'password' => 'password'])
76
->assertNoContent();
77
}
78
79
/**
80
* Tests that an email is not updated if the password provided in the request is not
81
* valid for the account.
82
*/
83
public function testEmailIsNotUpdatedWhenPasswordIsInvalid()
84
{
85
/** @var User $user */
86
$user = User::factory()->create();
87
88
$response = $this->actingAs($user)->putJson('/api/client/account/email', [
89
'email' => '[email protected]',
90
'password' => 'invalid',
91
]);
92
93
$response->assertStatus(Response::HTTP_BAD_REQUEST);
94
$response->assertJsonPath('errors.0.code', 'InvalidPasswordProvidedException');
95
$response->assertJsonPath('errors.0.detail', 'The password provided was invalid for this account.');
96
}
97
98
/**
99
* Tests that an email is not updated if an invalid email address is passed through
100
* in the request.
101
*/
102
public function testEmailIsNotUpdatedWhenNotValid()
103
{
104
/** @var User $user */
105
$user = User::factory()->create();
106
107
$response = $this->actingAs($user)->putJson('/api/client/account/email', [
108
'email' => '',
109
'password' => 'password',
110
]);
111
112
$response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);
113
$response->assertJsonPath('errors.0.meta.rule', 'required');
114
$response->assertJsonPath('errors.0.detail', 'The email field is required.');
115
116
$response = $this->actingAs($user)->putJson('/api/client/account/email', [
117
'email' => 'invalid',
118
'password' => 'password',
119
]);
120
121
$response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);
122
$response->assertJsonPath('errors.0.meta.rule', 'email');
123
$response->assertJsonPath('errors.0.detail', 'The email must be a valid email address.');
124
}
125
126
/**
127
* Test that the password for an account can be successfully updated.
128
*/
129
public function testPasswordIsUpdated()
130
{
131
$user = User::factory()->create();
132
133
// Assign the user to two servers, one as the owner the other as a subuser, both
134
// on different nodes to ensure our logic fires off correctly and the user has their
135
// credentials revoked on both nodes.
136
$server = $this->createServerModel(['owner_id' => $user->id]);
137
$server2 = $this->createServerModel();
138
Subuser::factory()->for($server2)->for($user)->create();
139
140
$initialHash = $user->password;
141
142
Bus::fake([RevokeSftpAccessJob::class]);
143
144
$this->actingAs($user)
145
->putJson('/api/client/account/password', [
146
'current_password' => 'password',
147
'password' => 'New_Password1',
148
'password_confirmation' => 'New_Password1',
149
])
150
->assertNoContent();
151
152
$user = $user->refresh();
153
154
$this->assertNotEquals($user->password, $initialHash);
155
$this->assertTrue(Hash::check('New_Password1', $user->password));
156
$this->assertFalse(Hash::check('password', $user->password));
157
158
$this->assertActivityFor('user:account.password-changed', $user, $user);
159
$this->assertNotEquals($server->node_id, $server2->node_id);
160
161
Bus::assertDispatchedTimes(RevokeSftpAccessJob::class, 2);
162
Bus::assertDispatched(fn (RevokeSftpAccessJob $job) => $job->user === $user->uuid && $job->target->is($server->node));
163
Bus::assertDispatched(fn (RevokeSftpAccessJob $job) => $job->user === $user->uuid && $job->target->is($server2->node));
164
}
165
166
/**
167
* Test that the password for an account is not updated if the current password is not
168
* provided correctly.
169
*/
170
public function testPasswordIsNotUpdatedIfCurrentPasswordIsInvalid()
171
{
172
/** @var User $user */
173
$user = User::factory()->create();
174
175
$response = $this->actingAs($user)->putJson('/api/client/account/password', [
176
'current_password' => 'invalid',
177
'password' => 'New_Password1',
178
'password_confirmation' => 'New_Password1',
179
]);
180
181
$response->assertStatus(Response::HTTP_BAD_REQUEST);
182
$response->assertJsonPath('errors.0.code', 'InvalidPasswordProvidedException');
183
$response->assertJsonPath('errors.0.detail', 'The password provided was invalid for this account.');
184
}
185
186
/**
187
* Test that a validation error is returned to the user if no password is provided or if
188
* the password is below the minimum password length.
189
*/
190
public function testErrorIsReturnedForInvalidRequestData()
191
{
192
$user = User::factory()->create();
193
194
$this->actingAs($user)->putJson('/api/client/account/password', [
195
'current_password' => 'password',
196
])
197
->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)
198
->assertJsonPath('errors.0.meta.rule', 'required');
199
200
$this->actingAs($user)->putJson('/api/client/account/password', [
201
'current_password' => 'password',
202
'password' => 'pass',
203
'password_confirmation' => 'pass',
204
])
205
->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)
206
->assertJsonPath('errors.0.meta.rule', 'min');
207
}
208
209
/**
210
* Test that a validation error is returned if the password passed in the request
211
* does not have a confirmation, or the confirmation is not the same as the password.
212
*/
213
public function testErrorIsReturnedIfPasswordIsNotConfirmed()
214
{
215
/** @var User $user */
216
$user = User::factory()->create();
217
218
$response = $this->actingAs($user)->putJson('/api/client/account/password', [
219
'current_password' => 'password',
220
'password' => 'New_Password1',
221
'password_confirmation' => 'Invalid_New_Password',
222
]);
223
224
$response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);
225
$response->assertJsonPath('errors.0.meta.rule', 'confirmed');
226
$response->assertJsonPath('errors.0.detail', 'The password confirmation does not match.');
227
}
228
}
229
230