Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pterodactyl
GitHub Repository: pterodactyl/panel
Path: blob/1.0-develop/app/Http/Controllers/Auth/LoginCheckpointController.php
10284 views
1
<?php
2
3
namespace Pterodactyl\Http\Controllers\Auth;
4
5
use Carbon\Carbon;
6
use Carbon\CarbonImmutable;
7
use Carbon\CarbonInterface;
8
use Pterodactyl\Models\User;
9
use Illuminate\Http\JsonResponse;
10
use PragmaRX\Google2FA\Google2FA;
11
use Illuminate\Support\Facades\Event;
12
use Illuminate\Contracts\Encryption\Encrypter;
13
use Illuminate\Database\Eloquent\ModelNotFoundException;
14
use Pterodactyl\Events\Auth\ProvidedAuthenticationToken;
15
use Pterodactyl\Http\Requests\Auth\LoginCheckpointRequest;
16
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
17
18
class LoginCheckpointController extends AbstractLoginController
19
{
20
private const TOKEN_EXPIRED_MESSAGE = 'The authentication token provided has expired, please refresh the page and try again.';
21
22
/**
23
* LoginCheckpointController constructor.
24
*/
25
public function __construct(
26
private Encrypter $encrypter,
27
private Google2FA $google2FA,
28
private ValidationFactory $validation,
29
) {
30
parent::__construct();
31
}
32
33
/**
34
* Handle a login where the user is required to provide a TOTP authentication
35
* token. Once a user has reached this stage it is assumed that they have already
36
* provided a valid username and password.
37
*
38
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
39
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
40
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
41
* @throws \Exception
42
* @throws \Illuminate\Validation\ValidationException
43
*/
44
public function __invoke(LoginCheckpointRequest $request): JsonResponse
45
{
46
if ($this->hasTooManyLoginAttempts($request)) {
47
$this->sendLockoutResponse($request);
48
}
49
50
$details = $request->session()->get('auth_confirmation_token');
51
if (!$this->hasValidSessionData($details)) {
52
$this->sendFailedLoginResponse($request, null, self::TOKEN_EXPIRED_MESSAGE);
53
}
54
55
if (!hash_equals($request->input('confirmation_token') ?? '', $details['token_value'])) {
56
$this->sendFailedLoginResponse($request);
57
}
58
59
try {
60
$user = User::query()->findOrFail($details['user_id']);
61
} catch (ModelNotFoundException) {
62
$this->sendFailedLoginResponse($request, null, self::TOKEN_EXPIRED_MESSAGE);
63
}
64
65
// Recovery tokens go through a slightly different pathway for usage.
66
if (!is_null($recoveryToken = $request->input('recovery_token'))) {
67
if ($this->isValidRecoveryToken($user, $recoveryToken)) {
68
Event::dispatch(new ProvidedAuthenticationToken($user, true));
69
70
return $this->sendLoginResponse($user, $request);
71
}
72
} else {
73
$decrypted = $this->encrypter->decrypt($user->totp_secret);
74
$oldTimestamp = $user->totp_authenticated_at
75
? (int) floor($user->totp_authenticated_at->unix() / $this->google2FA->getKeyRegeneration())
76
: null;
77
78
$verified = $this->google2FA->verifyKeyNewer(
79
$decrypted,
80
$request->input('authentication_code') ?? '',
81
$oldTimestamp,
82
config('pterodactyl.auth.2fa.window') ?? 1,
83
);
84
85
if ($verified !== false) {
86
$user->update(['totp_authenticated_at' => Carbon::now()]);
87
88
Event::dispatch(new ProvidedAuthenticationToken($user));
89
90
return $this->sendLoginResponse($user, $request);
91
}
92
}
93
94
$this->sendFailedLoginResponse($request, $user, !empty($recoveryToken) ? 'The recovery token provided is not valid.' : null);
95
}
96
97
/**
98
* Determines if a given recovery token is valid for the user account. If we find a matching token
99
* it will be deleted from the database.
100
*
101
* @throws \Exception
102
*/
103
protected function isValidRecoveryToken(User $user, string $value): bool
104
{
105
foreach ($user->recoveryTokens as $token) {
106
if (password_verify($value, $token->token)) {
107
$token->delete();
108
109
return true;
110
}
111
}
112
113
return false;
114
}
115
116
/**
117
* Determines if the data provided from the session is valid or not. This
118
* will return false if the data is invalid, or if more time has passed than
119
* was configured when the session was written.
120
*/
121
protected function hasValidSessionData(?array $data): bool
122
{
123
$validator = $this->validation->make($data ?? [], [
124
'user_id' => 'required|integer|min:1',
125
'token_value' => 'required|string',
126
'expires_at' => 'required',
127
]);
128
129
if ($validator->fails()) {
130
return false;
131
}
132
133
if (!$data['expires_at'] instanceof CarbonInterface) {
134
return false;
135
}
136
137
if ($data['expires_at']->isBefore(CarbonImmutable::now())) {
138
return false;
139
}
140
141
return true;
142
}
143
}
144
145