Skip to content

Commit 4cb95d8

Browse files
committed
Add test coverage for 2fa
1 parent fc261fe commit 4cb95d8

File tree

2 files changed

+162
-4
lines changed

2 files changed

+162
-4
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@ public function __construct(
6161
*/
6262
public function index(Request $request)
6363
{
64-
if ($request->user()->totp_enabled) {
64+
if ($request->user()->use_totp) {
6565
throw new BadRequestHttpException('Two-factor authentication is already enabled on this account.');
6666
}
6767

68-
return JsonResponse::create([
68+
return new JsonResponse([
6969
'data' => [
7070
'image_url_data' => $this->setupService->handle($request->user()),
7171
],
@@ -98,7 +98,7 @@ public function store(Request $request)
9898

9999
$this->toggleTwoFactorService->handle($request->user(), $request->input('code'), true);
100100

101-
return JsonResponse::create([], Response::HTTP_NO_CONTENT);
101+
return new JsonResponse([], Response::HTTP_NO_CONTENT);
102102
}
103103

104104
/**
@@ -124,6 +124,6 @@ public function delete(Request $request)
124124
'use_totp' => false,
125125
]);
126126

127-
return JsonResponse::create([], Response::HTTP_NO_CONTENT);
127+
return new JsonResponse([], Response::HTTP_NO_CONTENT);
128128
}
129129
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<?php
2+
3+
namespace Pterodactyl\Tests\Integration\Api\Client;
4+
5+
use Carbon\Carbon;
6+
use Pterodactyl\Models\User;
7+
use Illuminate\Http\Response;
8+
use PragmaRX\Google2FA\Google2FA;
9+
use Pterodactyl\Tests\Integration\IntegrationTestCase;
10+
11+
class TwoFactorControllerTest extends IntegrationTestCase
12+
{
13+
/**
14+
* Clean up after tests have run.
15+
*/
16+
protected function tearDown(): void
17+
{
18+
User::query()->forceDelete();
19+
20+
parent::tearDown();
21+
}
22+
23+
/**
24+
* Test that image data for enabling 2FA is returned by the endpoint and that the user
25+
* record in the database is updated as expected.
26+
*/
27+
public function testTwoFactorImageDataIsReturned()
28+
{
29+
/** @var \Pterodactyl\Models\User $user */
30+
$user = factory(User::class)->create(['use_totp' => false]);
31+
32+
$this->assertFalse($user->use_totp);
33+
$this->assertEmpty($user->totp_secret);
34+
$this->assertEmpty($user->totp_authenticated_at);
35+
36+
$response = $this->actingAs($user)->getJson('/api/client/account/two-factor');
37+
38+
$response->assertOk();
39+
$response->assertJsonStructure(['data' => ['image_url_data']]);
40+
41+
$user = $user->refresh();
42+
43+
$this->assertFalse($user->use_totp);
44+
$this->assertNotEmpty($user->totp_secret);
45+
$this->assertEmpty($user->totp_authenticated_at);
46+
}
47+
48+
/**
49+
* Test that an error is returned if the user's account already has 2FA enabled on it.
50+
*/
51+
public function testErrorIsReturnedWhenTwoFactorIsAlreadyEnabled()
52+
{
53+
/** @var \Pterodactyl\Models\User $user */
54+
$user = factory(User::class)->create(['use_totp' => true]);
55+
56+
$response = $this->actingAs($user)->getJson('/api/client/account/two-factor');
57+
58+
$response->assertStatus(Response::HTTP_BAD_REQUEST);
59+
$response->assertJsonPath('errors.0.code', 'BadRequestHttpException');
60+
$response->assertJsonPath('errors.0.detail', 'Two-factor authentication is already enabled on this account.');
61+
}
62+
63+
/**
64+
* Test that a validation error is thrown if invalid data is passed to the 2FA endpoint.
65+
*/
66+
public function testValidationErrorIsReturnedIfInvalidDataIsPassedToEnabled2FA()
67+
{
68+
/** @var \Pterodactyl\Models\User $user */
69+
$user = factory(User::class)->create(['use_totp' => false]);
70+
71+
$response = $this->actingAs($user)->postJson('/api/client/account/two-factor', [
72+
'code' => '',
73+
]);
74+
75+
$response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);
76+
$response->assertJsonPath('errors.0.code', 'required');
77+
}
78+
79+
/**
80+
* Tests that 2FA can be enabled on an account for the user.
81+
*/
82+
public function testTwoFactorCanBeEnabledOnAccount()
83+
{
84+
/** @var \Pterodactyl\Models\User $user */
85+
$user = factory(User::class)->create(['use_totp' => false]);
86+
87+
// Make the initial call to get the account setup for 2FA.
88+
$this->actingAs($user)->getJson('/api/client/account/two-factor')->assertOk();
89+
90+
$user = $user->refresh();
91+
$this->assertNotNull($user->totp_secret);
92+
93+
/** @var \PragmaRX\Google2FA\Google2FA $service */
94+
$service = $this->app->make(Google2FA::class);
95+
96+
$secret = decrypt($user->totp_secret);
97+
$token = $service->getCurrentOtp($secret);
98+
99+
$response = $this->actingAs($user)->postJson('/api/client/account/two-factor', [
100+
'code' => $token,
101+
]);
102+
103+
$response->assertStatus(Response::HTTP_NO_CONTENT);
104+
105+
$user = $user->refresh();
106+
107+
$this->assertTrue($user->use_totp);
108+
}
109+
110+
/**
111+
* Test that two factor authentication can be disabled on an account as long as the password
112+
* provided is valid for the account.
113+
*/
114+
public function testTwoFactorCanBeDisabledOnAccount()
115+
{
116+
Carbon::setTestNow(Carbon::now());
117+
118+
/** @var \Pterodactyl\Models\User $user */
119+
$user = factory(User::class)->create(['use_totp' => true]);
120+
121+
$response = $this->actingAs($user)->deleteJson('/api/client/account/two-factor', [
122+
'password' => 'invalid',
123+
]);
124+
125+
$response->assertStatus(Response::HTTP_BAD_REQUEST);
126+
$response->assertJsonPath('errors.0.code', 'BadRequestHttpException');
127+
$response->assertJsonPath('errors.0.detail', 'The password provided was not valid.');
128+
129+
$response = $this->actingAs($user)->deleteJson('/api/client/account/two-factor', [
130+
'password' => 'password',
131+
]);
132+
133+
$response->assertStatus(Response::HTTP_NO_CONTENT);
134+
135+
$user = $user->refresh();
136+
$this->assertFalse($user->use_totp);
137+
$this->assertNotNull($user->totp_authenticated_at);
138+
$this->assertSame(Carbon::now()->toIso8601String(), $user->totp_authenticated_at->toIso8601String());
139+
}
140+
141+
/**
142+
* Test that no error is returned when trying to disabled two factor on an account where it
143+
* was not enabled in the first place.
144+
*/
145+
public function testNoErrorIsReturnedIfTwoFactorIsNotEnabled()
146+
{
147+
Carbon::setTestNow(Carbon::now());
148+
149+
/** @var \Pterodactyl\Models\User $user */
150+
$user = factory(User::class)->create(['use_totp' => false]);
151+
152+
$response = $this->actingAs($user)->deleteJson('/api/client/account/two-factor', [
153+
'password' => 'password',
154+
]);
155+
156+
$response->assertStatus(Response::HTTP_NO_CONTENT);
157+
}
158+
}

0 commit comments

Comments
 (0)