22
33namespace Pterodactyl \Http \Controllers \Auth ;
44
5+ use Carbon \CarbonInterface ;
6+ use Carbon \CarbonImmutable ;
57use Pterodactyl \Models \User ;
6- use Illuminate \Auth \AuthManager ;
78use Illuminate \Http \JsonResponse ;
89use PragmaRX \Google2FA \Google2FA ;
9- use Illuminate \Contracts \Config \Repository ;
1010use Illuminate \Contracts \Encryption \Encrypter ;
1111use Illuminate \Database \Eloquent \ModelNotFoundException ;
1212use Pterodactyl \Http \Requests \Auth \LoginCheckpointRequest ;
13- use Illuminate \Contracts \Cache \Repository as CacheRepository ;
14- use Pterodactyl \Contracts \Repository \UserRepositoryInterface ;
15- use Pterodactyl \Repositories \Eloquent \RecoveryTokenRepository ;
13+ use Illuminate \Contracts \Validation \Factory as ValidationFactory ;
1614
1715class LoginCheckpointController extends AbstractLoginController
1816{
19- /**
20- * @var \Illuminate\Contracts\Cache\Repository
21- */
22- private $ cache ;
23-
24- /**
25- * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
26- */
27- private $ repository ;
17+ private const TOKEN_EXPIRED_MESSAGE = 'The authentication token provided has expired, please refresh the page and try again. ' ;
2818
29- /**
30- * @var \PragmaRX\Google2FA\Google2FA
31- */
32- private $ google2FA ;
19+ private ValidationFactory $ validation ;
3320
34- /**
35- * @var \Illuminate\Contracts\Encryption\Encrypter
36- */
37- private $ encrypter ;
21+ private Google2FA $ google2FA ;
3822
39- /**
40- * @var \Pterodactyl\Repositories\Eloquent\RecoveryTokenRepository
41- */
42- private $ recoveryTokenRepository ;
23+ private Encrypter $ encrypter ;
4324
4425 /**
4526 * LoginCheckpointController constructor.
4627 */
47- public function __construct (
48- AuthManager $ auth ,
49- Encrypter $ encrypter ,
50- Google2FA $ google2FA ,
51- Repository $ config ,
52- CacheRepository $ cache ,
53- RecoveryTokenRepository $ recoveryTokenRepository ,
54- UserRepositoryInterface $ repository
55- ) {
56- parent ::__construct ($ auth , $ config );
28+ public function __construct (Encrypter $ encrypter , Google2FA $ google2FA , ValidationFactory $ validation )
29+ {
30+ parent ::__construct ();
5731
5832 $ this ->google2FA = $ google2FA ;
59- $ this ->cache = $ cache ;
60- $ this ->repository = $ repository ;
6133 $ this ->encrypter = $ encrypter ;
62- $ this ->recoveryTokenRepository = $ recoveryTokenRepository ;
34+ $ this ->validation = $ validation ;
6335 }
6436
6537 /**
@@ -81,18 +53,20 @@ public function __invoke(LoginCheckpointRequest $request): JsonResponse
8153 $ this ->sendLockoutResponse ($ request );
8254 }
8355
84- $ token = $ request ->input ('confirmation_token ' );
56+ $ details = $ request ->session ()->get ('auth_confirmation_token ' );
57+ if (!$ this ->hasValidSessionData ($ details )) {
58+ $ this ->sendFailedLoginResponse ($ request , null , self ::TOKEN_EXPIRED_MESSAGE );
59+ }
60+
61+ if (!hash_equals ($ request ->input ('confirmation_token ' ) ?? '' , $ details ['token_value ' ])) {
62+ $ this ->sendFailedLoginResponse ($ request );
63+ }
64+
8565 try {
8666 /** @var \Pterodactyl\Models\User $user */
87- $ user = User::query ()->findOrFail ($ this -> cache -> get ( $ token , 0 ) );
67+ $ user = User::query ()->findOrFail ($ details [ ' user_id ' ] );
8868 } catch (ModelNotFoundException $ exception ) {
89- $ this ->incrementLoginAttempts ($ request );
90-
91- return $ this ->sendFailedLoginResponse (
92- $ request ,
93- null ,
94- 'The authentication token provided has expired, please refresh the page and try again. '
95- );
69+ $ this ->sendFailedLoginResponse ($ request , null , self ::TOKEN_EXPIRED_MESSAGE );
9670 }
9771
9872 // Recovery tokens go through a slightly different pathway for usage.
@@ -104,15 +78,11 @@ public function __invoke(LoginCheckpointRequest $request): JsonResponse
10478 $ decrypted = $ this ->encrypter ->decrypt ($ user ->totp_secret );
10579
10680 if ($ this ->google2FA ->verifyKey ($ decrypted , (string ) $ request ->input ('authentication_code ' ) ?? '' , config ('pterodactyl.auth.2fa.window ' ))) {
107- $ this ->cache ->delete ($ token );
108-
10981 return $ this ->sendLoginResponse ($ user , $ request );
11082 }
11183 }
11284
113- $ this ->incrementLoginAttempts ($ request );
114-
115- return $ this ->sendFailedLoginResponse ($ request , $ user , !empty ($ recoveryToken ) ? 'The recovery token provided is not valid. ' : null );
85+ $ this ->sendFailedLoginResponse ($ request , $ user , !empty ($ recoveryToken ) ? 'The recovery token provided is not valid. ' : null );
11686 }
11787
11888 /**
@@ -135,4 +105,35 @@ protected function isValidRecoveryToken(User $user, string $value)
135105
136106 return false ;
137107 }
108+
109+ /**
110+ * Determines if the data provided from the session is valid or not. This
111+ * will return false if the data is invalid, or if more time has passed than
112+ * was configured when the session was written.
113+ *
114+ * @param array $data
115+ * @return bool
116+ */
117+ protected function hasValidSessionData (array $ data ): bool
118+ {
119+ $ validator = $ this ->validation ->make ($ data , [
120+ 'user_id ' => 'required|integer|min:1 ' ,
121+ 'token_value ' => 'required|string ' ,
122+ 'expires_at ' => 'required ' ,
123+ ]);
124+
125+ if ($ validator ->fails ()) {
126+ return false ;
127+ }
128+
129+ if (!$ data ['expires_at ' ] instanceof CarbonInterface) {
130+ return false ;
131+ }
132+
133+ if ($ data ['expires_at ' ]->isBefore (CarbonImmutable::now ())) {
134+ return false ;
135+ }
136+
137+ return true ;
138+ }
138139}
0 commit comments