diff --git a/Modules/Core/Filament/Responses/LoginResponse.php b/Modules/Core/Filament/Responses/LoginResponse.php index ed6dbb76..28bf64f8 100644 --- a/Modules/Core/Filament/Responses/LoginResponse.php +++ b/Modules/Core/Filament/Responses/LoginResponse.php @@ -4,22 +4,42 @@ use Filament\Auth\Http\Responses\Contracts\LoginResponse as BaseLoginResponse; use Illuminate\Support\Str; +use Modules\Core\Enums\UserRole; +use Modules\Core\Models\Company; class LoginResponse implements BaseLoginResponse { - public const DEFAULT_COMPANY_CODE = 'ivplv2'; + private const DEFAULT_COMPANY_CODE = 'ivplv2'; public function toResponse($request): mixed { - $user = auth()->user(); - - $tenant = $user->companies() - ->whereRaw('LOWER(search_code) = ?', [self::DEFAULT_COMPANY_CODE]) - ->first() - ?? $user->companies()->first(); - - if ( ! $tenant) { - abort(403, 'No company found for your account. Please contact an administrator.'); + $user = auth()->user(); + $isElevated = collect(UserRole::elevated()) + ->contains(fn ($role) => $user->hasRole($role)); + + if ($isElevated) { + $tenant = Company::query() + ->whereRaw('LOWER(search_code) = ?', [self::DEFAULT_COMPANY_CODE]) + ->first() + ?? Company::query()->oldest('id')->first(); + + if (! $tenant) { + abort(500, trans('auth.fallback_company_not_found')); + } + + filament()->setTenant($tenant); + } else { + $tenant = $user->companies() + ->whereRaw('LOWER(search_code) = ?', [self::DEFAULT_COMPANY_CODE]) + ->first() + ?? $user->companies()->oldest('id')->first(); + + if (! $tenant) { + abort(500, trans('auth.no_company_found_for_user')); + } + + session(['current_company_id' => $tenant->id]); + filament()->setTenant($tenant); } session(['current_company_id' => $tenant->id]); diff --git a/Modules/Core/Tests/Feature/LoginRedirectTest.php b/Modules/Core/Tests/Feature/LoginRedirectTest.php new file mode 100644 index 00000000..c8348f77 --- /dev/null +++ b/Modules/Core/Tests/Feature/LoginRedirectTest.php @@ -0,0 +1,231 @@ +delete(); + Carbon::setTestNow(Carbon::parse('2026-01-01 00:00:00')); + filament()->setCurrentPanel(filament()->getPanel('company')); + } + + protected function tearDown(): void + { + Carbon::setTestNow(); + parent::tearDown(); + } + + private function activeUser(array $overrides = []): User + { + return User::factory()->create(array_merge([ + 'is_active' => true, + 'email_verified_at' => Carbon::now(), + 'password' => bcrypt('password'), + ], $overrides)); + } + + private function ivplv2Company(): Company + { + return Company::factory()->create([ + 'search_code' => 'ivplv2', + 'name' => 'InvoicePlane Corporation', + 'slug' => 'invoiceplane-corporation', + ]); + } + + private function elevatedRole(string $role): void + { + Role::query()->firstOrCreate(['name' => $role, 'guard_name' => 'web']); + } + + # region elevated users + + #[Test] + #[Group('authentication')] + #[Group('redirect')] + public function it_redirects_elevated_user_to_ivplv2_dashboard_after_login(): void + { + /* Arrange */ + $this->elevatedRole(UserRole::SUPER_ADMIN->value); + $this->ivplv2Company(); + + $user = $this->activeUser(['email' => 'super@example.com']); + $user->assignRole(UserRole::SUPER_ADMIN->value); + + /* Act */ + $response = Livewire::test(Login::class) + ->fillForm([ + 'email' => 'super@example.com', + 'password' => 'password', + ]) + ->call('authenticate'); + + /* Assert */ + $response->assertRedirect( + route('filament.company.pages.dashboard', ['tenant' => 'ivplv2']) + ); + $this->assertAuthenticated(); + } + + #[Test] + #[Group('authentication')] + #[Group('redirect')] + public function it_redirects_admin_user_to_ivplv2_dashboard_after_login(): void + { + /* Arrange */ + $this->elevatedRole(UserRole::ADMIN->value); + $this->ivplv2Company(); + + $user = $this->activeUser(['email' => 'admin@example.com']); + $user->assignRole(UserRole::ADMIN->value); + + /* Act */ + $response = Livewire::test(Login::class) + ->fillForm([ + 'email' => 'admin@example.com', + 'password' => 'password', + ]) + ->call('authenticate'); + + /* Assert */ + $response->assertRedirect( + route('filament.company.pages.dashboard', ['tenant' => 'ivplv2']) + ); + } + + #[Test] + #[Group('authentication')] + #[Group('redirect')] + public function it_falls_back_to_first_company_when_ivplv2_absent_for_elevated_user(): void + { + /* Arrange */ + $this->elevatedRole(UserRole::SUPER_ADMIN->value); + Company::factory()->create(['search_code' => 'acme']); + + $user = $this->activeUser(['email' => 'super@example.com']); + $user->assignRole(UserRole::SUPER_ADMIN->value); + + /* Act */ + $response = Livewire::test(Login::class) + ->fillForm([ + 'email' => 'super@example.com', + 'password' => 'password', + ]) + ->call('authenticate'); + + /* Assert */ + $response->assertRedirect( + route('filament.company.pages.dashboard', ['tenant' => 'acme']) + ); + } + + # endregion + + # region regular users + + #[Test] + #[Group('authentication')] + #[Group('redirect')] + public function it_redirects_regular_user_to_ivplv2_when_attached_to_it(): void + { + /* Arrange */ + $this->elevatedRole(UserRole::CUSTOMER_ADMIN->value); + $ivplv2 = $this->ivplv2Company(); + + $user = $this->activeUser(['email' => 'client@example.com']); + $user->assignRole(UserRole::CUSTOMER_ADMIN->value); + $user->companies()->attach($ivplv2->id); + + /* Act */ + $response = Livewire::test(Login::class) + ->fillForm([ + 'email' => 'client@example.com', + 'password' => 'password', + ]) + ->call('authenticate'); + + /* Assert */ + $response->assertRedirect( + route('filament.company.pages.dashboard', ['tenant' => 'ivplv2']) + ); + $this->assertAuthenticated(); + } + + #[Test] + #[Group('authentication')] + #[Group('redirect')] + public function it_prefers_ivplv2_over_other_companies_for_regular_user(): void + { + /* Arrange */ + $this->elevatedRole(UserRole::CUSTOMER_ADMIN->value); + $other = Company::factory()->create(['search_code' => 'acme']); + $ivplv2 = $this->ivplv2Company(); + + $user = $this->activeUser(['email' => 'client@example.com']); + $user->assignRole(UserRole::CUSTOMER_ADMIN->value); + // Attach other company first — ivplv2 should still win + $user->companies()->attach($other->id); + $user->companies()->attach($ivplv2->id); + + /* Act */ + $response = Livewire::test(Login::class) + ->fillForm([ + 'email' => 'client@example.com', + 'password' => 'password', + ]) + ->call('authenticate'); + + /* Assert */ + $response->assertRedirect( + route('filament.company.pages.dashboard', ['tenant' => 'ivplv2']) + ); + } + + #[Test] + #[Group('authentication')] + #[Group('redirect')] + public function it_falls_back_to_first_company_when_regular_user_is_not_attached_to_ivplv2(): void + { + /* Arrange */ + $this->elevatedRole(UserRole::CUSTOMER_ADMIN->value); + $otherCompany = Company::factory()->create(['search_code' => 'acme']); + + $user = $this->activeUser(['email' => 'client@example.com']); + $user->assignRole(UserRole::CUSTOMER_ADMIN->value); + $user->companies()->attach($otherCompany->id); + + /* Act */ + $response = Livewire::test(Login::class) + ->fillForm([ + 'email' => 'client@example.com', + 'password' => 'password', + ]) + ->call('authenticate'); + + /* Assert */ + $response->assertRedirect( + route('filament.company.pages.dashboard', ['tenant' => 'acme']) + ); + } + + # endregion +}