create([ 'email' => 'user@test.com', 'password' => Hash::make('password123'), ]); $this->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class); $response = $this->post(route('login'), [ 'name' => $user->email, 'password' => 'password123', ]); // Assert authenticated on web guard $this->assertTrue(Auth::guard('web')->check()); $this->assertEquals($user->id, Auth::guard('web')->id()); // Assert not authenticated on other guards $this->assertFalse(Auth::guard('organization')->check()); $this->assertFalse(Auth::guard('bank')->check()); $this->assertFalse(Auth::guard('admin')->check()); } /** * Test that authentication fails with invalid credentials */ public function test_cannot_authenticate_with_invalid_credentials() { $user = User::factory()->create([ 'email' => 'user@test.com', 'password' => Hash::make('correct-password'), ]); $this->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class); $this->post(route('login'), [ 'name' => $user->email, 'password' => 'wrong-password', ]); // Assert not authenticated on any guard $this->assertFalse(Auth::guard('web')->check()); $this->assertFalse(Auth::guard('organization')->check()); $this->assertFalse(Auth::guard('bank')->check()); $this->assertFalse(Auth::guard('admin')->check()); } /** * Test that a user cannot authenticate on wrong guard */ public function test_cannot_authenticate_user_on_organization_guard() { $user = User::factory()->create([ 'password' => Hash::make('password123'), ]); // Attempt to manually authenticate user on organization guard (should not work) $result = Auth::guard('organization')->attempt([ 'email' => $user->email, 'password' => 'password123', ]); $this->assertFalse($result); $this->assertFalse(Auth::guard('organization')->check()); } // ========================================== // GUARD ISOLATION TESTS // ========================================== /** * Test that web guard remains active when elevated guard is active */ public function test_web_guard_remains_active_with_elevated_guard() { $user = User::factory()->create(); $organization = Organization::factory()->create([ 'password' => Hash::make('org-password'), ]); // Link user to organization $user->organizations()->attach($organization->id); // Authenticate on web guard Auth::guard('web')->login($user); $this->assertTrue(Auth::guard('web')->check()); // Now authenticate on organization guard Auth::guard('organization')->login($organization); session(['active_guard' => 'organization']); // Both guards should be active $this->assertTrue(Auth::guard('web')->check(), 'Web guard should remain active'); $this->assertTrue(Auth::guard('organization')->check(), 'Organization guard should be active'); $this->assertEquals('organization', session('active_guard')); } /** * Test that only one elevated guard can be active at a time */ public function test_only_one_elevated_guard_active_at_time() { $user = User::factory()->create(); $organization = Organization::factory()->create([ 'password' => Hash::make('password'), ]); $bank = Bank::factory()->create([ 'password' => Hash::make('password'), ]); // Link user to both profiles $user->organizations()->attach($organization->id); $user->banksManaged()->attach($bank->id); // Authenticate on web guard first Auth::guard('web')->login($user); // Authenticate on organization guard Auth::guard('organization')->login($organization); session(['active_guard' => 'organization']); $this->assertTrue(Auth::guard('organization')->check()); // Now use SwitchGuardTrait to switch to bank (should logout organization) $controller = new class { use \App\Traits\SwitchGuardTrait; }; $controller->switchGuard('bank', $bank); // Only bank guard should be active among elevated guards $this->assertFalse(Auth::guard('organization')->check(), 'Organization guard should be logged out'); $this->assertTrue(Auth::guard('bank')->check(), 'Bank guard should be active'); $this->assertTrue(Auth::guard('web')->check(), 'Web guard should remain active'); $this->assertEquals('bank', session('active_guard')); } /** * Test switching guard logs out other elevated guards */ public function test_switching_guard_logs_out_other_elevated_guards() { $user = User::factory()->create(); $organization = Organization::factory()->create(['password' => Hash::make('password')]); $bank = Bank::factory()->create(['password' => Hash::make('password')]); $admin = Admin::factory()->create(['password' => Hash::make('password')]); // Link user to all profiles $user->organizations()->attach($organization->id); $user->banksManaged()->attach($bank->id); $user->admins()->attach($admin->id); Auth::guard('web')->login($user); // Use SwitchGuardTrait $controller = new class { use \App\Traits\SwitchGuardTrait; }; // Switch to organization $controller->switchGuard('organization', $organization); $this->assertTrue(Auth::guard('organization')->check()); $this->assertFalse(Auth::guard('bank')->check()); $this->assertFalse(Auth::guard('admin')->check()); // Switch to bank $controller->switchGuard('bank', $bank); $this->assertFalse(Auth::guard('organization')->check(), 'Organization should be logged out'); $this->assertTrue(Auth::guard('bank')->check()); $this->assertFalse(Auth::guard('admin')->check()); // Switch to admin $controller->switchGuard('admin', $admin); $this->assertFalse(Auth::guard('organization')->check()); $this->assertFalse(Auth::guard('bank')->check(), 'Bank should be logged out'); $this->assertTrue(Auth::guard('admin')->check()); } // ========================================== // SESSION STATE MANAGEMENT TESTS // ========================================== /** * Test that active guard is stored in session */ public function test_active_guard_stored_in_session() { $user = User::factory()->create(); $organization = Organization::factory()->create(['password' => Hash::make('password')]); $user->organizations()->attach($organization->id); Auth::guard('web')->login($user); $controller = new class { use \App\Traits\SwitchGuardTrait; }; $controller->switchGuard('organization', $organization); $this->assertEquals('organization', session('active_guard')); } /** * Test that logging out non-web guards sets active guard to web */ public function test_logout_non_web_guards_sets_active_guard_to_web() { $user = User::factory()->create(); $organization = Organization::factory()->create(['password' => Hash::make('password')]); $user->organizations()->attach($organization->id); Auth::guard('web')->login($user); $controller = new class { use \App\Traits\SwitchGuardTrait; }; $controller->switchGuard('organization', $organization); $this->assertEquals('organization', session('active_guard')); // Logout from non-web guards $controller->logoutNonWebGuards(); $this->assertEquals('web', session('active_guard')); $this->assertFalse(Auth::guard('organization')->check()); $this->assertTrue(Auth::guard('web')->check()); } // ========================================== // AUTHENTICATION EDGE CASES // ========================================== /** * Test that guest cannot access authenticated routes */ public function test_guest_cannot_access_authenticated_routes() { $response = $this->get(route('main')); $response->assertRedirect(); } /** * Test that authenticated user can access web guard routes */ public function test_authenticated_user_can_access_web_guard_routes() { $user = User::factory()->create(); $this->actingAs($user, 'web'); $response = $this->get(route('main')); $response->assertStatus(200); } /** * Test authentication persists across requests */ public function test_authentication_persists_across_requests() { $user = User::factory()->create([ 'email' => 'user@test.com', 'password' => Hash::make('password123'), ]); $this->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class); // First request - login $this->post(route('login'), [ 'name' => $user->email, 'password' => 'password123', ]); $this->assertTrue(Auth::guard('web')->check()); // Second request - should still be authenticated $response = $this->get(route('main')); $this->assertTrue(Auth::guard('web')->check()); $response->assertStatus(200); } /** * Test that logging out clears authentication */ public function test_logging_out_clears_authentication() { $user = User::factory()->create(); $this->actingAs($user, 'web'); $this->assertTrue(Auth::guard('web')->check()); Auth::guard('web')->logout(); $this->assertFalse(Auth::guard('web')->check()); } // ========================================== // CYCLOS LEGACY PASSWORD MIGRATION TESTS // ========================================== /** * Test that legacy Cyclos passwords are migrated on successful login */ public function test_cyclos_password_migrated_on_successful_organization_login() { $user = User::factory()->create(); $organization = Organization::factory()->create([ 'cyclos_salt' => 'legacy_salt', 'password' => strtolower(hash('sha256', 'legacy_salt' . 'old-password')), ]); $user->organizations()->attach($organization->id); // Store intent in session session([ 'intended_profile_switch_type' => 'Organization', 'intended_profile_switch_id' => $organization->id, ]); $this->actingAs($user, 'web'); $this->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class); // Attempt login with old password $response = $this->post(route('organization.login.post'), [ 'password' => 'old-password', ]); // Refresh organization from database $organization->refresh(); // Assert password was rehashed and salt removed $this->assertNull($organization->cyclos_salt, 'Cyclos salt should be removed'); $this->assertTrue(Hash::check('old-password', $organization->password), 'Password should be rehashed with Laravel Hash'); // Assert authentication succeeded $this->assertTrue(Auth::guard('organization')->check()); } }