From b9aeda1cba8d03859d831b98a21b6651787f8f64 Mon Sep 17 00:00:00 2001 From: Nabeel Shahzad Date: Sat, 16 May 2020 11:46:25 -0400 Subject: [PATCH] Enable logins using pilot ID #698 --- app/Exceptions/PilotIdNotFound.php | 43 +++++++++++ app/Http/Controllers/Auth/LoginController.php | 76 +++++++++++++++++-- app/Services/UserService.php | 42 ++++++++++ resources/lang/en/common.php | 2 + resources/lang/es/common.php | 2 + resources/lang/it/common.php | 2 + .../layouts/default/auth/login.blade.php | 2 +- .../views/vendor/mail/text/button.blade.php | 1 + .../views/vendor/mail/text/footer.blade.php | 1 + .../views/vendor/mail/text/header.blade.php | 1 + .../views/vendor/mail/text/layout.blade.php | 9 +++ .../views/vendor/mail/text/message.blade.php | 27 +++++++ .../views/vendor/mail/text/panel.blade.php | 1 + .../views/vendor/mail/text/subcopy.blade.php | 1 + .../views/vendor/mail/text/table.blade.php | 1 + tests/UserTest.php | 31 ++++++++ 16 files changed, 234 insertions(+), 8 deletions(-) create mode 100644 app/Exceptions/PilotIdNotFound.php create mode 100644 resources/views/vendor/mail/text/button.blade.php create mode 100644 resources/views/vendor/mail/text/footer.blade.php create mode 100644 resources/views/vendor/mail/text/header.blade.php create mode 100644 resources/views/vendor/mail/text/layout.blade.php create mode 100644 resources/views/vendor/mail/text/message.blade.php create mode 100644 resources/views/vendor/mail/text/panel.blade.php create mode 100644 resources/views/vendor/mail/text/subcopy.blade.php create mode 100644 resources/views/vendor/mail/text/table.blade.php diff --git a/app/Exceptions/PilotIdNotFound.php b/app/Exceptions/PilotIdNotFound.php new file mode 100644 index 00000000..34d9ef63 --- /dev/null +++ b/app/Exceptions/PilotIdNotFound.php @@ -0,0 +1,43 @@ +pilot_id = $pilot_id; + parent::__construct( + 404, + 'Pilot '.$pilot_id.' not found' + ); + } + + /** + * Return the RFC 7807 error type (without the URL root) + */ + public function getErrorType(): string + { + return 'pilot-id-not-found'; + } + + /** + * Get the detailed error string + */ + public function getErrorDetails(): string + { + return $this->getMessage(); + } + + /** + * Return an array with the error details, merged with the RFC7807 response + */ + public function getErrorMetadata(): array + { + return [ + 'pilot_id' => $this->pilot_id, + ]; + } +} diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 40c4550a..11970d43 100755 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -3,36 +3,98 @@ namespace App\Http\Controllers\Auth; use App\Contracts\Controller; +use App\Exceptions\PilotIdNotFound; use App\Models\Enums\UserState; +use App\Services\UserService; use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Log; -/** - * Class LoginController - */ class LoginController extends Controller { use AuthenticatesUsers; protected $redirectTo = '/dashboard'; + /** @var UserService */ + private $userSvc; + + /** @var string */ + private $loginFieldValue; + /** * LoginController constructor. + * + * @param UserService $userSvc */ - public function __construct() + public function __construct(UserService $userSvc) { $this->redirectTo = config('phpvms.login_redirect'); $this->middleware('guest', ['except' => 'logout']); + $this->userSvc = $userSvc; } /** - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * Get the needed authorization credentials from the request. + * Overriding the value from the trait + * + * @override + * + * @param \Illuminate\Http\Request $request + * + * @return array */ - public function showLoginForm() + protected function credentials(Request $request) { - return view('auth/login'); + return [ + 'email' => $this->loginFieldValue, + 'password' => $request->input('password'), + ]; + } + + /** + * Validate the user login request. + * + * @override + * + * @param \Illuminate\Http\Request $request + * + * @return void + */ + protected function validateLogin(Request $request) + { + $id_field = $request->input('email'); + $validations = ['required', 'string']; + + /* + * Trying to login by email or not? + * + * If not, run a validation rule which attempts to split the user by their VA and ID + * Then inject that user's email into the request + */ + if (strpos($id_field, '@') !== false) { + $validations[] = 'email'; + $this->loginFieldValue = $request->input('email'); + } else { + $validations[] = function ($attr, $value, $fail) use ($request) { + try { + $user = $this->userSvc->findUserByPilotId($value); + } catch (PilotIdNotFound $ex) { + Log::warning('Error logging in, pilot_id not found, id='.$value); + $fail('Pilot not found'); + return; + } + + $request->email = $user->email; + $this->loginFieldValue = $user->email; + }; + } + + $request->validate([ + 'email' => $validations, + 'password' => 'required|string', + ]); } /** diff --git a/app/Services/UserService.php b/app/Services/UserService.php index 42b955e2..185d57cd 100644 --- a/app/Services/UserService.php +++ b/app/Services/UserService.php @@ -6,6 +6,7 @@ use App\Contracts\Service; use App\Events\UserRegistered; use App\Events\UserStateChanged; use App\Events\UserStatsChanged; +use App\Exceptions\PilotIdNotFound; use App\Exceptions\UserPilotIdExists; use App\Models\Enums\PirepState; use App\Models\Enums\UserState; @@ -14,6 +15,7 @@ use App\Models\Rank; use App\Models\Role; use App\Models\User; use App\Repositories\AircraftRepository; +use App\Repositories\AirlineRepository; use App\Repositories\SubfleetRepository; use App\Repositories\UserRepository; use App\Support\Units\Time; @@ -25,6 +27,7 @@ use function is_array; class UserService extends Service { private $aircraftRepo; + private $airlineRepo; private $subfleetRepo; private $userRepo; @@ -32,15 +35,18 @@ class UserService extends Service * UserService constructor. * * @param AircraftRepository $aircraftRepo + * @param AirlineRepository $airlineRepo * @param SubfleetRepository $subfleetRepo * @param UserRepository $userRepo */ public function __construct( AircraftRepository $aircraftRepo, + AirlineRepository $airlineRepo, SubfleetRepository $subfleetRepo, UserRepository $userRepo ) { $this->aircraftRepo = $aircraftRepo; + $this->airlineRepo = $airlineRepo; $this->subfleetRepo = $subfleetRepo; $this->userRepo = $userRepo; } @@ -178,6 +184,42 @@ class UserService extends Service return $user; } + /** + * Split a given pilot ID into an airline and ID portions + * + * @param string $pilot_id + */ + public function findUserByPilotId(string $pilot_id) + { + $airlines = $this->airlineRepo->all(['id', 'icao', 'iata']); + + $ident_str = null; + $pilot_id = strtoupper($pilot_id); + foreach ($airlines as $airline) { + if (strpos($pilot_id, $airline->icao) !== false) { + $ident_str = $airline->icao; + break; + } + + if (strpos($pilot_id, $airline->iata) !== false) { + $ident_str = $airline->iata; + break; + } + } + + if (empty($ident_str)) { + throw new PilotIdNotFound($pilot_id); + } + + $parsed_pilot_id = str_replace($ident_str, '', $pilot_id); + $user = User::where(['airline_id' => $airline->id, 'pilot_id' => $parsed_pilot_id])->first(); + if (empty($user)) { + throw new PilotIdNotFound($pilot_id); + } + + return $user; + } + /** * Return the subfleets this user is allowed access to, * based on their current rank diff --git a/resources/lang/en/common.php b/resources/lang/en/common.php index 04120738..f28687cc 100644 --- a/resources/lang/en/common.php +++ b/resources/lang/en/common.php @@ -10,6 +10,7 @@ return [ 'newestpilots' => 'Newest Pilots', 'profile' => 'Profile', 'email' => 'Email', + 'pilot_id' => 'Pilot ID', 'register' => 'Register', 'login' => 'Log In', 'logout' => 'Log Out', @@ -48,6 +49,7 @@ return [ 'inactive' => 'Inactive', 'yes' => 'Yes', 'no' => 'No', + 'or' => 'or', 'days' => [ 'mon' => 'Monday', 'tues' => 'Tuesday', diff --git a/resources/lang/es/common.php b/resources/lang/es/common.php index 5d49f711..1b4e4f3e 100644 --- a/resources/lang/es/common.php +++ b/resources/lang/es/common.php @@ -10,6 +10,7 @@ return [ 'newestpilots' => 'Nuevos pilotos', 'profile' => 'Perfil', 'email' => 'Email', + 'pilot_id' => 'Pilot ID', 'register' => 'Registrarse', 'login' => 'Iniciar sesión', 'logout' => 'Cerrar sesión', @@ -48,6 +49,7 @@ return [ 'inactive' => 'Inactivo', 'yes' => 'Sí', 'no' => 'No', + 'or' => 'o', 'days' => [ 'mon' => 'lunes', 'tues' => 'martes', diff --git a/resources/lang/it/common.php b/resources/lang/it/common.php index a58f9f6f..0cb5a2a5 100644 --- a/resources/lang/it/common.php +++ b/resources/lang/it/common.php @@ -10,6 +10,7 @@ return [ 'newestpilots' => 'Ultimi Piloti', 'profile' => 'Profilo', 'email' => 'Email', + 'pilot_id' => 'Pilot ID', 'login' => 'Accesso', 'logout' => 'Uscita', 'register' => 'Registrazione', @@ -48,6 +49,7 @@ return [ 'inactive' => 'Inattivo', 'yes' => 'Sì', 'no' => 'No', + 'or' => 'o', 'days' => [ 'mon' => 'Lunedì', 'tues' => 'Martedì', diff --git a/resources/views/layouts/default/auth/login.blade.php b/resources/views/layouts/default/auth/login.blade.php index d47f11e9..63412977 100644 --- a/resources/views/layouts/default/auth/login.blade.php +++ b/resources/views/layouts/default/auth/login.blade.php @@ -21,7 +21,7 @@ {{ Form::text('email', old('email'), [ 'id' => 'email', - 'placeholder' => __('common.email'), + 'placeholder' => __('common.email').' '.__('common.or').' '.__('common.pilot_id'), 'class' => 'form-control', 'required' => true, ]) diff --git a/resources/views/vendor/mail/text/button.blade.php b/resources/views/vendor/mail/text/button.blade.php new file mode 100644 index 00000000..97444ebd --- /dev/null +++ b/resources/views/vendor/mail/text/button.blade.php @@ -0,0 +1 @@ +{{ $slot }}: {{ $url }} diff --git a/resources/views/vendor/mail/text/footer.blade.php b/resources/views/vendor/mail/text/footer.blade.php new file mode 100644 index 00000000..3338f620 --- /dev/null +++ b/resources/views/vendor/mail/text/footer.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/resources/views/vendor/mail/text/header.blade.php b/resources/views/vendor/mail/text/header.blade.php new file mode 100644 index 00000000..aaa3e575 --- /dev/null +++ b/resources/views/vendor/mail/text/header.blade.php @@ -0,0 +1 @@ +[{{ $slot }}]({{ $url }}) diff --git a/resources/views/vendor/mail/text/layout.blade.php b/resources/views/vendor/mail/text/layout.blade.php new file mode 100644 index 00000000..5c634c9f --- /dev/null +++ b/resources/views/vendor/mail/text/layout.blade.php @@ -0,0 +1,9 @@ +{!! strip_tags($header) !!} + +{!! strip_tags($slot) !!} +@isset($subcopy) + + {!! strip_tags($subcopy) !!} +@endisset + +{!! strip_tags($footer) !!} diff --git a/resources/views/vendor/mail/text/message.blade.php b/resources/views/vendor/mail/text/message.blade.php new file mode 100644 index 00000000..59f841bb --- /dev/null +++ b/resources/views/vendor/mail/text/message.blade.php @@ -0,0 +1,27 @@ +@component('mail::layout') + {{-- Header --}} + @slot('header') + @component('mail::header', ['url' => config('app.url')]) + {{ config('app.name') }} + @endcomponent + @endslot + + {{-- Body --}} + {{ $slot }} + + {{-- Subcopy --}} + @isset($subcopy) + @slot('subcopy') + @component('mail::subcopy') + {{ $subcopy }} + @endcomponent + @endslot + @endisset + + {{-- Footer --}} + @slot('footer') + @component('mail::footer') + © {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.') + @endcomponent + @endslot +@endcomponent diff --git a/resources/views/vendor/mail/text/panel.blade.php b/resources/views/vendor/mail/text/panel.blade.php new file mode 100644 index 00000000..3338f620 --- /dev/null +++ b/resources/views/vendor/mail/text/panel.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/resources/views/vendor/mail/text/subcopy.blade.php b/resources/views/vendor/mail/text/subcopy.blade.php new file mode 100644 index 00000000..3338f620 --- /dev/null +++ b/resources/views/vendor/mail/text/subcopy.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/resources/views/vendor/mail/text/table.blade.php b/resources/views/vendor/mail/text/table.blade.php new file mode 100644 index 00000000..3338f620 --- /dev/null +++ b/resources/views/vendor/mail/text/table.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/tests/UserTest.php b/tests/UserTest.php index 7b74d19d..be0d218e 100644 --- a/tests/UserTest.php +++ b/tests/UserTest.php @@ -1,5 +1,6 @@ userSvc->changePilotId($user1, 2); } + /** + * Make sure that the splitting of the user ID works + */ + public function testUserPilotIdSplit(): void + { + /** @var \App\Models\User $user */ + $user = factory(App\Models\User::class)->create(); + $found_user = $this->userSvc->findUserByPilotId($user->ident); + $this->assertEquals($user->id, $found_user->id); + + // Look for them with the IATA code + $found_user = $this->userSvc->findUserByPilotId($user->airline->iata.$user->id); + $this->assertEquals($user->id, $found_user->id); + } + + /** + * Pilot ID not found + */ + public function testUserPilotIdSplitInvalidId(): void + { + /** @var \App\Models\User $user */ + $user = factory(App\Models\User::class)->create(); + + $this->expectException(PilotIdNotFound::class); + $this->userSvc->findUserByPilotId($user->airline->iata); + } + /** * Test the pilot ID being added when a new user is created */