33namespace Pterodactyl \Services \Users ;
44
55use Carbon \Carbon ;
6+ use Illuminate \Support \Str ;
67use Pterodactyl \Models \User ;
78use PragmaRX \Google2FA \Google2FA ;
89use Illuminate \Contracts \Encryption \Encrypter ;
910use Pterodactyl \Contracts \Repository \UserRepositoryInterface ;
11+ use Pterodactyl \Repositories \Eloquent \RecoveryTokenRepository ;
1012use Pterodactyl \Exceptions \Service \User \TwoFactorAuthenticationTokenInvalid ;
1113
1214class ToggleTwoFactorService
@@ -26,21 +28,29 @@ class ToggleTwoFactorService
2628 */
2729 private $ repository ;
2830
31+ /**
32+ * @var \Pterodactyl\Repositories\Eloquent\RecoveryTokenRepository
33+ */
34+ private $ recoveryTokenRepository ;
35+
2936 /**
3037 * ToggleTwoFactorService constructor.
3138 *
3239 * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
3340 * @param \PragmaRX\Google2FA\Google2FA $google2FA
41+ * @param \Pterodactyl\Repositories\Eloquent\RecoveryTokenRepository $recoveryTokenRepository
3442 * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository
3543 */
3644 public function __construct (
3745 Encrypter $ encrypter ,
3846 Google2FA $ google2FA ,
47+ RecoveryTokenRepository $ recoveryTokenRepository ,
3948 UserRepositoryInterface $ repository
4049 ) {
4150 $ this ->encrypter = $ encrypter ;
4251 $ this ->google2FA = $ google2FA ;
4352 $ this ->repository = $ repository ;
53+ $ this ->recoveryTokenRepository = $ recoveryTokenRepository ;
4454 }
4555
4656 /**
@@ -49,7 +59,7 @@ public function __construct(
4959 * @param \Pterodactyl\Models\User $user
5060 * @param string $token
5161 * @param bool|null $toggleState
52- * @return bool
62+ * @return string[]
5363 *
5464 * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
5565 * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
@@ -58,23 +68,50 @@ public function __construct(
5868 * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
5969 * @throws \Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid
6070 */
61- public function handle (User $ user , string $ token , bool $ toggleState = null ): bool
71+ public function handle (User $ user , string $ token , bool $ toggleState = null ): array
6272 {
6373 $ secret = $ this ->encrypter ->decrypt ($ user ->totp_secret );
6474
6575 $ isValidToken = $ this ->google2FA ->verifyKey ($ secret , $ token , config ()->get ('pterodactyl.auth.2fa.window ' ));
6676
6777 if (! $ isValidToken ) {
68- throw new TwoFactorAuthenticationTokenInvalid (
69- 'The token provided is not valid. '
70- );
78+ throw new TwoFactorAuthenticationTokenInvalid ('The token provided is not valid. ' );
79+ }
80+
81+ // Now that we're enabling 2FA on the account, generate 10 recovery tokens for the account
82+ // and store them hashed in the database. We'll return them to the caller so that the user
83+ // can see and save them.
84+ //
85+ // If a user is unable to login with a 2FA token they can provide one of these backup codes
86+ // which will then be marked as deleted from the database and will also bypass 2FA protections
87+ // on their account.
88+ $ tokens = [];
89+ if ((! $ toggleState && ! $ user ->use_totp ) || $ toggleState ) {
90+ $ inserts = [];
91+ for ($ i = 0 ; $ i < 10 ; $ i ++) {
92+ $ token = Str::random (10 );
93+
94+ $ inserts [] = [
95+ 'user_id ' => $ user ->id ,
96+ 'token ' => password_hash ($ token , PASSWORD_DEFAULT ),
97+ ];
98+
99+ $ tokens [] = $ token ;
100+ }
101+
102+ // Bulk insert the hashed tokens.
103+ $ this ->recoveryTokenRepository ->insert ($ inserts );
104+ } elseif ($ toggleState === false || $ user ->use_totp ) {
105+ // If we are disabling 2FA on this account we will delete all of the recovery codes
106+ // that exist in the database for this account.
107+ $ this ->recoveryTokenRepository ->deleteWhere (['user_id ' => $ user ->id ]);
71108 }
72109
73110 $ this ->repository ->withoutFreshModel ()->update ($ user ->id , [
74111 'totp_authenticated_at ' => Carbon::now (),
75112 'use_totp ' => (is_null ($ toggleState ) ? ! $ user ->use_totp : $ toggleState ),
76113 ]);
77114
78- return true ;
115+ return $ tokens ;
79116 }
80117}
0 commit comments