diff --git a/app/Http/Controllers/Api/AirportController.php b/app/Http/Controllers/Api/AirportController.php index 6f4d9245..1d3ec372 100644 --- a/app/Http/Controllers/Api/AirportController.php +++ b/app/Http/Controllers/Api/AirportController.php @@ -19,6 +19,18 @@ class AirportController extends AppBaseController $this->airportRepo = $airportRepo; } + /** + * Do a lookup, via vaCentral, for the airport information + * @param $id + * @return AirportResource + */ + public function get($id) + { + $id = strtoupper($id); + AirportResource::withoutWrapping(); + return new AirportResource($this->airportRepo->find($id)); + } + /** * Do a lookup, via vaCentral, for the airport information * @param $id diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index d9d81f1d..7c69f41d 100755 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -47,6 +47,7 @@ class Kernel extends HttpKernel * @var array */ protected $routeMiddleware = [ + 'api.auth' => \App\Http\Middleware\ApiAuth::class, 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, diff --git a/app/Http/Middleware/ApiAuth.php b/app/Http/Middleware/ApiAuth.php new file mode 100644 index 00000000..8809b025 --- /dev/null +++ b/app/Http/Middleware/ApiAuth.php @@ -0,0 +1,67 @@ +header('Authorization')) { + return $this->unauthorized(); + } + + // Try to find the user via API key. Cache this lookup + $api_key = $request->header('Authorization'); + $user = Cache::remember( + config('cache.keys.USER_API_KEY.key') . $api_key, + config('cache.keys.USER_API_KEY.time'), + function () use ($api_key) { + return User::where('apikey', $api_key)->first(); + } + ); + + if(!$user) { + return $this->unauthorized(); + } + + // Set the user to the request + Auth::setUser($user); + $request->merge(['user' => $user]); + $request->setUserResolver(function () use ($user) { + return $user; + }); + + return $next($request); + } + + /** + * Return an unauthorized message + * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response + */ + private function unauthorized() + { + return response([ + 'error' => [ + 'code' => '401', + 'http_code' => 'Unauthorized', + 'message' => 'Invalid or missing API key', + ], + ], 401); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index cceb8f8a..4a4444c0 100755 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -96,6 +96,16 @@ class User extends Authenticatable ]; + /** + * Returns a 40 character API key that a user can use + * @return string + */ + public static function generateApiKey() + { + $key = sha1(time() . mt_rand()); + return $key; + } + public function pilot_id() { return $this->airline->icao.str_pad($this->id, 3, '0', STR_PAD_LEFT); diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index c77e47e8..a0e23d5a 100755 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -68,7 +68,7 @@ class RouteServiceProvider extends ServiceProvider Route::group([ 'middleware' => [ 'api', - //\App\Http\Middleware\MeasureExecutionTime::class + 'api.auth', ], 'namespace' => $this->namespace."\\API", 'prefix' => 'api', diff --git a/app/Services/PilotService.php b/app/Services/PilotService.php index 3f857dc3..e645b0b4 100644 --- a/app/Services/PilotService.php +++ b/app/Services/PilotService.php @@ -67,23 +67,26 @@ class PilotService extends BaseService public function createPilot(array $data) { - $user = User::create(['name' => $data['name'], - 'email' => $data['email'], - 'airline_id' => $data['airline'], - 'home_airport_id' => $data['home_airport'], - 'curr_airport_id' => $data['home_airport'], - 'password' => Hash::make($data['password'])]); + $user = User::create([ + 'name' => $data['name'], + 'email' => $data['email'], + 'apikey' => User::generateApiKey(), + 'airline_id' => $data['airline'], + 'home_airport_id' => $data['home_airport'], + 'curr_airport_id' => $data['home_airport'], + 'password' => Hash::make($data['password']) + ]); + # Attach the user roles $role = Role::where('name', 'user')->first(); $user->attachRole($role); + # Let's check their rank $this->calculatePilotRank($user); - event(new UserRegistered($user)); # TODO: Send out an email + event(new UserRegistered($user)); - # Looking good, let's return their information return $user; } - } diff --git a/config/cache.php b/config/cache.php index c3d7acae..28add1cc 100755 --- a/config/cache.php +++ b/config/cache.php @@ -8,12 +8,16 @@ return [ 'keys' => [ 'AIRPORT_VACENTRAL_LOOKUP' => [ 'key' => 'airports:lookup:', - 'time' => 1800, + 'time' => 60 * 30, ], 'RANKS_PILOT_LIST' => [ 'key' => 'ranks:pilot_list', - 'time' => 600, - ] + 'time' => 60 * 10, + ], + 'USER_API_KEY' => [ + 'key' => 'user:apikey', + 'time' => 60 * 5, // 5 min + ], ], 'stores' => [ diff --git a/database/migrations/2017_06_08_0000_create_users_table.php b/database/migrations/2017_06_08_0000_create_users_table.php index b47c7c80..61212fbc 100755 --- a/database/migrations/2017_06_08_0000_create_users_table.php +++ b/database/migrations/2017_06_08_0000_create_users_table.php @@ -18,6 +18,7 @@ class CreateUsersTable extends Migration $table->string('name')->nullable(); $table->string('email')->unique(); $table->string('password'); + $table->string('apikey', 40)->nullable(); $table->integer('airline_id')->nullable()->unsigned(); $table->integer('rank_id')->nullable()->unsigned(); $table->string('home_airport_id', 5)->nullable(); @@ -33,6 +34,7 @@ class CreateUsersTable extends Migration $table->softDeletes(); $table->index('email'); + $table->index('apikey'); }); // Create table for storing roles diff --git a/routes/api.php b/routes/api.php index 0bb87b81..fc55481c 100755 --- a/routes/api.php +++ b/routes/api.php @@ -17,6 +17,7 @@ Route::group([], function () { Route::match(['get'], 'status', 'BaseController@status'); + Route::match(['get'], 'airports/{id}', 'AirportController@get'); Route::match(['get'], 'airports/{id}/lookup', 'AirportController@lookup'); Route::match(['get'], 'flight/{id}', 'FlightController@get'); diff --git a/tests/ApiTest.php b/tests/ApiTest.php new file mode 100644 index 00000000..e0bc0cc3 --- /dev/null +++ b/tests/ApiTest.php @@ -0,0 +1,54 @@ + 'testapikey' + ]; + + public function setUp() + { + parent::setUp(); + $this->addData('base'); + } + + /** + * Ensure authentication against the API works + */ + public function testApiAuthentication() + { + $uri = '/api/airports/kjfk'; + + // Missing auth header + $this->get($uri)->assertStatus(401); + + // Test invalid API key + $this->withHeaders(['Authorization' => 'invalidKey'])->get($uri) + ->assertStatus(401); + + // Test upper/lower case of Authorization header, etc + $this->withHeaders(self::$headers)->get($uri) + ->assertStatus(200) + ->assertJson(['icao' => 'KJFK'], true); + + $this->withHeaders(['AUTHORIZATION' => 'testapikey'])->get($uri) + ->assertStatus(200) + ->assertJson(['icao' => 'KJFK'], true); + } + + /** + * Make sure the airport data is returned + */ + public function testAirportRequest() + { + $this->withHeaders(self::$headers)->get('/api/airports/KJFK') + ->assertStatus(200) + ->assertJson(['icao' => 'KJFK'], true); + + $this->withHeaders(self::$headers)->get('/api/airports/UNK') + ->assertStatus(404); + } +} diff --git a/tests/data/airports.yml b/tests/data/airports.yml index 59443e23..002b874f 100644 --- a/tests/data/airports.yml +++ b/tests/data/airports.yml @@ -1,19 +1,19 @@ airports: - - id: 1 + - id: KAUS iata: AUS icao: KAUS name: Austin-Bergstrom location: Austin, Texas, USA lat: 30.1945278 lon: -97.6698889 - - id: 2 + - id: KJFK iata: JFK icao: KJFK name: John F Kennedy location: New York, New York, USA lat: 40.6399257 lon: -73.7786950 - - id: 3 + - id: EGLL iata: LHR icao: EGLL name: London Heathrow diff --git a/tests/data/base.yml b/tests/data/base.yml index 990d9d7c..9c87aa84 100644 --- a/tests/data/base.yml +++ b/tests/data/base.yml @@ -13,6 +13,7 @@ users: name: Admin User email: admin@phpvms.net password: admin + apikey: testapikey rank_id: 1 created_at: now updated_at: now @@ -42,14 +43,14 @@ ranks: auto_promote: 0 airports: - - id: 1 + - id: KAUS icao: KAUS name: Austin-Bergstrom location: Austin, Texas, USA lat: 30.1945278 lon: -97.6698889 fuel_jeta_cost: 100 - - id: 2 + - id: KJFK icao: KJFK name: John F Kennedy location: New York, New York, USA