Skip to content

Commit c7c2c1a

Browse files
authored
Implement changes to 2FA system (pterodactyl#761)
1 parent a0c96f2 commit c7c2c1a

File tree

18 files changed

+358
-296
lines changed

18 files changed

+358
-296
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
1717

1818
### Changed
1919
* 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.
20+
* Two-Factor authentication tokens are now 32 bytes in length, and are stored encrypted at rest in the database.
2021

2122
## v0.7.0-beta.1 (Derelict Dermodactylus)
2223
### Added

app/Http/Controllers/Auth/LoginController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ public function totpCheckpoint(Request $request)
202202
return $this->sendFailedLoginResponse($request);
203203
}
204204

205-
if (! $G2FA->verifyKey($user->totp_secret, $request->input('2fa_token'), 2)) {
205+
if (! $G2FA->verifyKey(Crypt::decrypt($user->totp_secret), $request->input('2fa_token'), 2)) {
206206
event(new \Illuminate\Auth\Events\Failed($user, $credentials));
207207

208208
return $this->sendFailedLoginResponse($request);

app/Http/Controllers/Base/SecurityController.php

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727

2828
use Illuminate\Http\Request;
2929
use Prologue\Alerts\AlertsMessageBag;
30-
use Illuminate\Contracts\Session\Session;
3130
use Pterodactyl\Http\Controllers\Controller;
3231
use Pterodactyl\Services\Users\TwoFactorSetupService;
3332
use Pterodactyl\Services\Users\ToggleTwoFactorService;
@@ -52,11 +51,6 @@ class SecurityController extends Controller
5251
*/
5352
protected $repository;
5453

55-
/**
56-
* @var \Illuminate\Contracts\Session\Session
57-
*/
58-
protected $session;
59-
6054
/**
6155
* @var \Pterodactyl\Services\Users\ToggleTwoFactorService
6256
*/
@@ -72,23 +66,20 @@ class SecurityController extends Controller
7266
*
7367
* @param \Prologue\Alerts\AlertsMessageBag $alert
7468
* @param \Illuminate\Contracts\Config\Repository $config
75-
* @param \Illuminate\Contracts\Session\Session $session
7669
* @param \Pterodactyl\Contracts\Repository\SessionRepositoryInterface $repository
7770
* @param \Pterodactyl\Services\Users\ToggleTwoFactorService $toggleTwoFactorService
7871
* @param \Pterodactyl\Services\Users\TwoFactorSetupService $twoFactorSetupService
7972
*/
8073
public function __construct(
8174
AlertsMessageBag $alert,
8275
ConfigRepository $config,
83-
Session $session,
8476
SessionRepositoryInterface $repository,
8577
ToggleTwoFactorService $toggleTwoFactorService,
8678
TwoFactorSetupService $twoFactorSetupService
8779
) {
8880
$this->alert = $alert;
8981
$this->config = $config;
9082
$this->repository = $repository;
91-
$this->session = $session;
9283
$this->toggleTwoFactorService = $toggleTwoFactorService;
9384
$this->twoFactorSetupService = $twoFactorSetupService;
9485
}
@@ -122,7 +113,9 @@ public function index(Request $request)
122113
*/
123114
public function generateTotp(Request $request)
124115
{
125-
return response()->json($this->twoFactorSetupService->handle($request->user()));
116+
return response()->json([
117+
'qrImage' => $this->twoFactorSetupService->handle($request->user()),
118+
]);
126119
}
127120

128121
/**

app/Models/User.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class User extends Model implements
6363
'language',
6464
'use_totp',
6565
'totp_secret',
66+
'totp_authenticated_at',
6667
'gravatar',
6768
'root_admin',
6869
];
@@ -78,6 +79,11 @@ class User extends Model implements
7879
'gravatar' => 'boolean',
7980
];
8081

82+
/**
83+
* @var array
84+
*/
85+
protected $dates = [self::CREATED_AT, self::UPDATED_AT, 'totp_authenticated_at'];
86+
8187
/**
8288
* The attributes excluded from the model's JSON form.
8389
*

app/Services/Users/ToggleTwoFactorService.php

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,82 @@
11
<?php
2-
/**
3-
* Pterodactyl - Panel
4-
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
5-
*
6-
* This software is licensed under the terms of the MIT license.
7-
* https://opensource.org/licenses/MIT
8-
*/
92

103
namespace Pterodactyl\Services\Users;
114

5+
use Carbon\Carbon;
126
use Pterodactyl\Models\User;
13-
use PragmaRX\Google2FA\Contracts\Google2FA;
7+
use PragmaRX\Google2FA\Google2FA;
8+
use Illuminate\Contracts\Config\Repository;
9+
use Illuminate\Contracts\Encryption\Encrypter;
1410
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
1511
use Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid;
1612

1713
class ToggleTwoFactorService
1814
{
1915
/**
20-
* @var \PragmaRX\Google2FA\Contracts\Google2FA
16+
* @var \Illuminate\Contracts\Config\Repository
2117
*/
22-
protected $google2FA;
18+
private $config;
19+
20+
/**
21+
* @var \Illuminate\Contracts\Encryption\Encrypter
22+
*/
23+
private $encrypter;
24+
25+
/**
26+
* @var \PragmaRX\Google2FA\Google2FA
27+
*/
28+
private $google2FA;
2329

2430
/**
2531
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
2632
*/
27-
protected $repository;
33+
private $repository;
2834

2935
/**
3036
* ToggleTwoFactorService constructor.
3137
*
32-
* @param \PragmaRX\Google2FA\Contracts\Google2FA $google2FA
38+
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
39+
* @param \PragmaRX\Google2FA\Google2FA $google2FA
40+
* @param \Illuminate\Contracts\Config\Repository $config
3341
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository
3442
*/
3543
public function __construct(
44+
Encrypter $encrypter,
3645
Google2FA $google2FA,
46+
Repository $config,
3747
UserRepositoryInterface $repository
3848
) {
49+
$this->config = $config;
50+
$this->encrypter = $encrypter;
3951
$this->google2FA = $google2FA;
4052
$this->repository = $repository;
4153
}
4254

4355
/**
44-
* @param int|\Pterodactyl\Models\User $user
45-
* @param string $token
46-
* @param null|bool $toggleState
56+
* Toggle 2FA on an account only if the token provided is valid.
57+
*
58+
* @param \Pterodactyl\Models\User $user
59+
* @param string $token
60+
* @param bool|null $toggleState
4761
* @return bool
4862
*
4963
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
5064
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
5165
* @throws \Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid
5266
*/
53-
public function handle($user, $token, $toggleState = null)
67+
public function handle(User $user, string $token, bool $toggleState = null): bool
5468
{
55-
if (! $user instanceof User) {
56-
$user = $this->repository->find($user);
57-
}
69+
$window = $this->config->get('pterodactyl.auth.2fa.window');
70+
$secret = $this->encrypter->decrypt($user->totp_secret);
71+
72+
$isValidToken = $this->google2FA->verifyKey($secret, $token, $window);
5873

59-
if (! $this->google2FA->verifyKey($user->totp_secret, $token, 2)) {
74+
if (! $isValidToken) {
6075
throw new TwoFactorAuthenticationTokenInvalid;
6176
}
6277

6378
$this->repository->withoutFresh()->update($user->id, [
79+
'totp_authenticated_at' => Carbon::now(),
6480
'use_totp' => (is_null($toggleState) ? ! $user->use_totp : $toggleState),
6581
]);
6682

app/Services/Users/TwoFactorSetupService.php

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
namespace Pterodactyl\Services\Users;
1111

1212
use Pterodactyl\Models\User;
13-
use PragmaRX\Google2FA\Contracts\Google2FA;
13+
use PragmaRX\Google2FA\Google2FA;
14+
use Illuminate\Contracts\Encryption\Encrypter;
1415
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
1516
use Illuminate\Contracts\Config\Repository as ConfigRepository;
1617

@@ -19,58 +20,62 @@ class TwoFactorSetupService
1920
/**
2021
* @var \Illuminate\Contracts\Config\Repository
2122
*/
22-
protected $config;
23+
private $config;
2324

2425
/**
25-
* @var \PragmaRX\Google2FA\Contracts\Google2FA
26+
* @var \Illuminate\Contracts\Encryption\Encrypter
2627
*/
27-
protected $google2FA;
28+
private $encrypter;
29+
30+
/**
31+
* @var \PragmaRX\Google2FA\Google2FA
32+
*/
33+
private $google2FA;
2834

2935
/**
3036
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
3137
*/
32-
protected $repository;
38+
private $repository;
3339

3440
/**
3541
* TwoFactorSetupService constructor.
3642
*
3743
* @param \Illuminate\Contracts\Config\Repository $config
38-
* @param \PragmaRX\Google2FA\Contracts\Google2FA $google2FA
44+
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
45+
* @param \PragmaRX\Google2FA\Google2FA $google2FA
3946
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository
4047
*/
4148
public function __construct(
4249
ConfigRepository $config,
50+
Encrypter $encrypter,
4351
Google2FA $google2FA,
4452
UserRepositoryInterface $repository
4553
) {
4654
$this->config = $config;
55+
$this->encrypter = $encrypter;
4756
$this->google2FA = $google2FA;
4857
$this->repository = $repository;
4958
}
5059

5160
/**
52-
* Generate a 2FA token and store it in the database.
61+
* Generate a 2FA token and store it in the database before returning the
62+
* QR code image.
5363
*
54-
* @param int|\Pterodactyl\Models\User $user
55-
* @return array
64+
* @param \Pterodactyl\Models\User $user
65+
* @return string
5666
*
5767
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
5868
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
5969
*/
60-
public function handle($user)
70+
public function handle(User $user): string
6171
{
62-
if (! $user instanceof User) {
63-
$user = $this->repository->find($user);
64-
}
65-
66-
$secret = $this->google2FA->generateSecretKey();
72+
$secret = $this->google2FA->generateSecretKey($this->config->get('pterodactyl.auth.2fa.bytes'));
6773
$image = $this->google2FA->getQRCodeGoogleUrl($this->config->get('app.name'), $user->email, $secret);
6874

69-
$this->repository->withoutFresh()->update($user->id, ['totp_secret' => $secret]);
75+
$this->repository->withoutFresh()->update($user->id, [
76+
'totp_secret' => $this->encrypter->encrypt($secret),
77+
]);
7078

71-
return [
72-
'qrImage' => $image,
73-
'secret' => $secret,
74-
];
79+
return $image;
7580
}
7681
}

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"mtdowling/cron-expression": "^1.2",
3232
"nesbot/carbon": "^1.22",
3333
"nicolaslopezj/searchable": "^1.9",
34-
"pragmarx/google2fa": "^1.0",
34+
"pragmarx/google2fa": "^2.0",
3535
"predis/predis": "^1.1",
3636
"prologue/alerts": "^0.4",
3737
"ramsey/uuid": "^3.7",
@@ -46,7 +46,7 @@
4646
"require-dev": {
4747
"barryvdh/laravel-debugbar": "^2.4",
4848
"barryvdh/laravel-ide-helper": "^2.4",
49-
"friendsofphp/php-cs-fixer": "^2.4",
49+
"friendsofphp/php-cs-fixer": "^2.8.0",
5050
"fzaninotto/faker": "^1.6",
5151
"mockery/mockery": "^0.9",
5252
"php-mock/php-mock-phpunit": "^1.1",

0 commit comments

Comments
 (0)