Path: blob/1.0-develop/app/Services/Users/ToggleTwoFactorService.php
10277 views
<?php12namespace Pterodactyl\Services\Users;34use Carbon\Carbon;5use Illuminate\Support\Str;6use Pterodactyl\Models\User;7use PragmaRX\Google2FA\Google2FA;8use Illuminate\Database\ConnectionInterface;9use Illuminate\Contracts\Encryption\Encrypter;10use Pterodactyl\Contracts\Repository\UserRepositoryInterface;11use Pterodactyl\Repositories\Eloquent\RecoveryTokenRepository;12use Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid;1314class ToggleTwoFactorService15{16/**17* ToggleTwoFactorService constructor.18*/19public function __construct(20private ConnectionInterface $connection,21private Encrypter $encrypter,22private Google2FA $google2FA,23private RecoveryTokenRepository $recoveryTokenRepository,24private UserRepositoryInterface $repository,25) {26}2728/**29* Toggle 2FA on an account only if the token provided is valid.30*31* @throws \Throwable32* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException33* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException34* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException35* @throws TwoFactorAuthenticationTokenInvalid36*/37public function handle(User $user, string $token, ?bool $toggleState = null): array38{39$secret = $this->encrypter->decrypt($user->totp_secret);4041$isValidToken = $this->google2FA->verifyKey($secret, $token, config()->get('pterodactyl.auth.2fa.window'));4243if (!$isValidToken) {44throw new TwoFactorAuthenticationTokenInvalid();45}4647return $this->connection->transaction(function () use ($user, $toggleState) {48// Now that we're enabling 2FA on the account, generate 10 recovery tokens for the account49// and store them hashed in the database. We'll return them to the caller so that the user50// can see and save them.51//52// If a user is unable to login with a 2FA token they can provide one of these backup codes53// which will then be marked as deleted from the database and will also bypass 2FA protections54// on their account.55$tokens = [];56if ((!$toggleState && !$user->use_totp) || $toggleState) {57$inserts = [];58for ($i = 0; $i < 10; ++$i) {59$token = Str::random(10);6061$inserts[] = [62'user_id' => $user->id,63'token' => password_hash($token, PASSWORD_DEFAULT),64// insert() won't actually set the time on the models, so make sure we do this65// manually here.66'created_at' => Carbon::now(),67];6869$tokens[] = $token;70}7172// Before inserting any new records make sure all of the old ones are deleted to avoid73// any issues or storing an unnecessary number of tokens in the database.74$this->recoveryTokenRepository->deleteWhere(['user_id' => $user->id]);7576// Bulk insert the hashed tokens.77$this->recoveryTokenRepository->insert($inserts);78}7980$this->repository->withoutFreshModel()->update($user->id, [81'totp_authenticated_at' => null,82'use_totp' => (is_null($toggleState) ? !$user->use_totp : $toggleState),83]);8485return $tokens;86});87}88}899091