diff --git a/backend/app/Http/Kernel.php b/backend/app/Http/Kernel.php new file mode 100644 index 0000000..7a3930b --- /dev/null +++ b/backend/app/Http/Kernel.php @@ -0,0 +1,68 @@ + + */ + protected $middleware = [ + // \App\Http\Middleware\TrustHosts::class, + \App\Http\Middleware\TrustProxies::class, + \Illuminate\Http\Middleware\HandleCors::class, + \App\Http\Middleware\PreventRequestsDuringMaintenance::class, + \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, + \App\Http\Middleware\TrimStrings::class, + \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, + ]; + + /** + * The application's route middleware groups. + * + * @var array> + */ + protected $middlewareGroups = [ + 'web' => [ + \App\Http\Middleware\EncryptCookies::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + \Illuminate\Session\Middleware\StartSession::class, + \Illuminate\View\Middleware\ShareErrorsFromSession::class, + \App\Http\Middleware\VerifyCsrfToken::class, + \Illuminate\Routing\Middleware\SubstituteBindings::class, + ], + + 'api' => [ + \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, + \Illuminate\Routing\Middleware\ThrottleRequests::class.':api', + \Illuminate\Routing\Middleware\SubstituteBindings::class, + ], + ]; + + /** + * The application's middleware aliases. + * + * Aliases may be used instead of class names to conveniently assign middleware to routes and groups. + * + * @var array + */ + protected $middlewareAliases = [ + 'auth' => \App\Http\Middleware\Authenticate::class, + 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, + 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class, + 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, + 'can' => \Illuminate\Auth\Middleware\Authorize::class, + 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, + 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, + 'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class, + 'signed' => \App\Http\Middleware\ValidateSignature::class, + 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, + 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + ]; +} diff --git a/backend/config/cors.php b/backend/config/cors.php index 8a39e6d..7d207e3 100644 --- a/backend/config/cors.php +++ b/backend/config/cors.php @@ -1,34 +1,12 @@ ['api/*', 'sanctum/csrf-cookie'], - 'allowed_methods' => ['*'], - - 'allowed_origins' => ['*'], - + 'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:3000')], 'allowed_origins_patterns' => [], - 'allowed_headers' => ['*'], - 'exposed_headers' => [], - 'max_age' => 0, - - 'supports_credentials' => false, - + 'supports_credentials' => true, ]; diff --git a/backend/config/sanctum.php b/backend/config/sanctum.php index 35d75b3..92db33e 100644 --- a/backend/config/sanctum.php +++ b/backend/config/sanctum.php @@ -17,8 +17,8 @@ 'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( '%s%s', - 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', - Sanctum::currentApplicationUrlWithPort() + 'localhost,localhost:3000,localhost:80,127.0.0.1,127.0.0.1:8000', + \Illuminate\Support\Str::contains(env('APP_URL', ''), 'https://') ? ','.parse_url(env('APP_URL', ''), PHP_URL_HOST) : '' ))), /* diff --git a/backend/routes/api.php b/backend/routes/api.php new file mode 100644 index 0000000..c780b14 --- /dev/null +++ b/backend/routes/api.php @@ -0,0 +1,36 @@ +validate([ + 'email' => ['required', 'email'], + 'password' => ['required'], + ]); + + if (! auth()->attempt($credentials)) { + return response()->json(['message' => 'Invalid credentials'], 401); + } + + if ($request->hasSession()) { + $request->session()->regenerate(); + } + + return response()->json(['data' => auth()->user()]); +}); + +Route::post('/logout', function (Request $request) { + auth()->logout(); + if ($request->hasSession()) { + $request->session()->invalidate(); + $request->session()->regenerateToken(); + } + return response()->json([], 204); +})->middleware('auth:sanctum'); + +Route::get('/user', function (Request $request) { + return response()->json(['data' => $request->user()]); +})->middleware('auth:sanctum'); + +// Module routes added in Task 10 diff --git a/backend/tests/Feature/Auth/AuthTest.php b/backend/tests/Feature/Auth/AuthTest.php new file mode 100644 index 0000000..f03bbd5 --- /dev/null +++ b/backend/tests/Feature/Auth/AuthTest.php @@ -0,0 +1,41 @@ +postJson('/api/login', [ + 'email' => 'nobody@example.com', + 'password' => 'wrong', + ]); + + $response->assertStatus(401); +}); + +it('logs in with valid credentials and returns user', function () { + $user = User::factory()->create([ + 'email' => 'staff@example.com', + 'password' => bcrypt('password'), + ]); + + $response = $this->postJson('/api/login', [ + 'email' => 'staff@example.com', + 'password' => 'password', + ]); + + $response->assertStatus(200) + ->assertJsonPath('data.email', 'staff@example.com'); +}); + +it('returns 401 on /api/user when unauthenticated', function () { + $response = $this->getJson('/api/user'); + $response->assertStatus(401); +}); + +it('returns user on /api/user when authenticated', function () { + $user = User::factory()->create(); + $response = $this->actingAs($user)->getJson('/api/user'); + $response->assertStatus(200)->assertJsonPath('data.id', $user->id); +});