NFarrington/vatsim-url-shortener

View on GitHub
app/Http/Controllers/Platform/VatsimConnectLoginController.php

Summary

Maintainability
A
3 hrs
Test Coverage
F
0%
<?php

namespace App\Http\Controllers\Platform;

use App\Entities\User;
use App\Repositories\UserRepository;
use Carbon\Carbon;
use Closure;
use Doctrine\ORM\EntityManagerInterface;
use Illuminate\Foundation\Auth\RedirectsUsers;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Http\Request;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Provider\GenericProvider;
use Log;

class VatsimConnectLoginController extends Controller
{
    use RedirectsUsers, ThrottlesLogins;

    const VATSIM_CONNECT_SESSION_STATE_KEY = 'vatsim-connect-auth-state';

    private EntityManagerInterface $em;
    private GenericProvider $provider;
    private UserRepository $userRepository;

    public function __construct(EntityManagerInterface $em, UserRepository $userRepository)
    {
        $this->middleware('guest');
        $this->em = $em;
        $this->userRepository = $userRepository;

        $this->middleware(function (Request $request, Closure $next) {
            $this->provider = new GenericProvider(
                [
                    'clientId' => config('vatsim-connect.client_id'),
                    'clientSecret' => config('vatsim-connect.client_secret'),
                    'redirectUri' => route('platform.login.vatsim-connect'),
                    'urlAuthorize' => config('vatsim-connect.base_url').'/oauth/authorize',
                    'urlAccessToken' => config('vatsim-connect.base_url').'/oauth/token',
                    'urlResourceOwnerDetails' => config('vatsim-connect.base_url').'/api/user',
                    'scopes' => config('vatsim-connect.scopes'),
                    'scopeSeparator' => ' ',
                ]
            );

            return $next($request);
        });
    }

    protected function redirectTo()
    {
        return route('platform.dashboard');
    }

    public function username()
    {
        return 'id';
    }

    protected function throttleKey(Request $request)
    {
        return $request->ip();
    }

    public function maxAttempts()
    {
        return 20;
    }

    public function login(Request $request)
    {
        if ($request->has('error')) {
            $request->session()->forget(self::VATSIM_CONNECT_SESSION_STATE_KEY);

            return redirect()->route('platform.login')
                ->with('error', "VATSIM Connect login failed: {$request->get('error')}. Please try again.");
        }

        if (!$request->has('code') || !$request->has('state')) {
            $authorizationUrl = $this->provider->getAuthorizationUrl();

            $request->session()->put(self::VATSIM_CONNECT_SESSION_STATE_KEY, $this->provider->getState());

            return redirect()->to($authorizationUrl);
        }

        if ($request->input('state') !== $request->session()->pull(self::VATSIM_CONNECT_SESSION_STATE_KEY)) {
            Log::info('Login failure: invalid state.');

            return redirect()
                ->route('platform.login')
                ->with('error', 'VATSIM Connect login failed: invalid state. Please try again.');
        }

        return $this->verifyLogin($request);
    }

    private function verifyLogin(Request $request)
    {
        try {
            $accessToken = $this->provider->getAccessToken(
                'authorization_code',
                [
                    'code' => $request->input('code'),
                ]
            );
        } catch (IdentityProviderException $e) {
            Log::info("Login failure: IdentityProviderException. {$e->getMessage()}");

            return redirect()
                ->route('platform.login')
                ->with('error', 'VATSIM Connect login failed: IdentityProviderException. Please try again.');
        }

        $resourceOwner = json_decode(
            json_encode(
                $this->provider->getResourceOwner($accessToken)->toArray()
            )
        );

        if (!isset($resourceOwner->data) || !isset($resourceOwner->data->cid)) {
            Log::info('Login failure: missing details.', ['resourceOwner' => $resourceOwner]);

            return redirect()
                ->route('platform.login')
                ->with('error', 'VATSIM Connect login failed: required details missing. Please try again.');
        }

        $user = $this->userRepository->find($resourceOwner->data->cid);
        if ($user === null) {
            $user = new User();
            $user->setId((int) $resourceOwner->data->cid);
        }
        if ($resourceOwner->data->oauth->token_valid === 'true') {
            $user->setVatsimConnectAccessToken($accessToken->getToken());
            $user->setVatsimConnectRefreshToken($accessToken->getRefreshToken());
            $tokenExpiry = $accessToken->getExpires();
            $user->setVatsimConnectTokenExpiry(
                $tokenExpiry === null ? null : Carbon::createFromTimestampUTC($tokenExpiry)
            );
        }
        $user->setFirstName($resourceOwner->data->personal->name_first ?? '');
        $user->setLastName($resourceOwner->data->personal->name_last ?? '');
        if (!empty($resourceOwner->data->personal->email ?? '')) {
            $user->setEmail($resourceOwner->data->personal->email);
            $user->setEmailVerified(true);
        }

        $this->em->persist($user);
        $this->em->flush();

        $atcRating = $resourceOwner->data->vatsim->rating->id ?? null;
        $pilotRating = $resourceOwner->data->vatsim->pilotrating->id ?? null;
        if (is_null($atcRating) && is_null($pilotRating)) {
            Log::info('Login failure: no rating.', ['resourceOwner' => $resourceOwner]);

            return redirect()->route('platform.login')
                ->with('error', 'VATSIM Connect login failed. You must have an ATC or pilot rating to log in.');
        }

        if (in_array($user->getId(), config('auth.banned_users'))) {
            Log::info('Login failure: banned user.', ['resourceOwner' => $resourceOwner]);

            return redirect()->route('platform.login')
                ->with('error', 'VATSIM Connect login failed. You are not authorized to use this service.');
        }

        $guardName = config('auth.defaults.guard');
        $remember = config("auth.guards.{$guardName}.remember", false);
        auth()->loginUsingId($user->getId(), $remember);
        $request->session()->regenerate();
        $this->clearLoginAttempts($request);

        return redirect()->intended($this->redirectPath());
    }
}