# Story 1.2: Authentication & Role System ## Status Draft ## Epic Reference **Epic 1:** Core Foundation & Infrastructure ## Story **As an** admin, **I want** a secure authentication system with Admin/Client roles, **So that** only authorized users can access the platform with appropriate permissions. ## Acceptance Criteria ### Functional Requirements 1. [ ] Fortify configured with custom Volt views 2. [ ] Login page with bilingual support (Arabic/English) 3. [ ] Session timeout after 2 hours of inactivity 4. [ ] Rate limiting on login attempts (5 attempts per minute) 5. [ ] Admin role with full access to all features 6. [ ] Client role with restricted access (own data only) 7. [ ] Registration feature DISABLED (admin creates all accounts) ### Security Requirements 8. [ ] CSRF protection enabled on all forms 9. [ ] Password hashing using bcrypt 10. [ ] Custom middleware for admin authorization 11. [ ] Secure session configuration 12. [ ] Remember me functionality (optional) ### Integration Requirements 13. [ ] Login redirects to appropriate dashboard: - Admin users → `/admin/dashboard` (placeholder until Epic 6) - Client users → `/client/dashboard` (placeholder until Epic 7) 14. [ ] Logout clears session properly and redirects to login page 15. [ ] Admin middleware protects admin-only routes (return 403 for unauthorized) 16. [ ] Failed login attempts logged to `admin_logs` table 17. [ ] Deactivated users cannot log in ### Quality Requirements 18. [ ] Login form validates inputs properly 19. [ ] Error messages are clear and bilingual 20. [ ] All test scenarios in "Testing" section pass 21. [ ] No security vulnerabilities ## Tasks / Subtasks - [ ] **Task 1: Configure Fortify** (AC: 1, 7) - [ ] Update `config/fortify.php` to disable registration feature - [ ] Keep `resetPasswords` enabled (admin-triggered only via future Epic) - [ ] Keep `emailVerification`, `updateProfileInformation`, `updatePasswords` enabled - [ ] Keep `twoFactorAuthentication` enabled with confirm options - [ ] **Task 2: Add User Model Helper Methods** (AC: 5, 6) - [ ] Add `isAdmin(): bool` method to `app/Models/User.php` - [ ] Add `isClient(): bool` method to `app/Models/User.php` - [ ] Add `isIndividual(): bool` method to `app/Models/User.php` - [ ] Add `isCompany(): bool` method to `app/Models/User.php` - [ ] **Task 3: Create Custom Middleware** (AC: 10, 15, 17) - [ ] Create `app/Http/Middleware/EnsureUserIsAdmin.php` - [ ] Create `app/Http/Middleware/EnsureUserIsActive.php` - [ ] Register middleware aliases in `bootstrap/app.php` as `admin` and `active` - [ ] Add `SetLocale` middleware to web group (for bilingual support) - [ ] **Task 4: Create Login Volt Component** (AC: 1, 2, 8, 12, 18, 19) - [ ] Create `resources/views/livewire/auth/login.blade.php` as class-based Volt component - [ ] Implement form with email and password fields using Flux UI components - [ ] Add remember me checkbox - [ ] Add CSRF token via `@csrf` or Livewire handling - [ ] Display validation errors in current locale - [ ] Add language switcher or respect current locale - [ ] **Task 5: Configure Login Redirect Logic** (AC: 13, 14) - [ ] Update `FortifyServiceProvider` to set custom login view - [ ] Create `app/Actions/Fortify/RedirectIfAuthenticated.php` or configure in `AuthenticatedSessionController` - [ ] Implement redirect logic: admin → `/admin/dashboard`, client → `/client/dashboard` - [ ] Create placeholder routes for dashboards (return simple "Dashboard coming soon" view) - [ ] Implement logout redirect to login page - [ ] **Task 6: Implement Session and Rate Limiting** (AC: 3, 4) - [ ] Set `SESSION_LIFETIME=120` in `.env` (2 hours) - [ ] Verify Fortify's built-in rate limiting (5 attempts per minute) - [ ] Ensure rate limit error message uses translation key - [ ] **Task 7: Implement Login Attempt Logging** (AC: 16) - [ ] Create listener for `Illuminate\Auth\Events\Failed` event - [ ] Log failed attempts to `admin_logs` table with IP address - [ ] Register listener in `EventServiceProvider` or `AppServiceProvider` - [ ] **Task 8: Implement Deactivated User Check** (AC: 17) - [ ] Add check in `EnsureUserIsActive` middleware - [ ] Or customize Fortify's `authenticateUsing` callback to reject deactivated users - [ ] Return bilingual error message for deactivated accounts - [ ] **Task 9: Write Tests** (AC: 20) - [ ] Create `tests/Feature/Auth/LoginTest.php` with all login flow tests - [ ] Create `tests/Feature/Auth/AuthorizationTest.php` for middleware tests - [ ] Create `tests/Unit/Models/UserHelperMethodsTest.php` for isAdmin/isClient tests - [ ] Run all tests and ensure they pass - [ ] **Task 10: Final Verification** (AC: 21) - [ ] Run `vendor/bin/pint` to format code - [ ] Verify no security vulnerabilities (CSRF, session fixation, etc.) - [ ] Test full login flow in browser for both admin and client users ## Dev Notes ### Relevant Source Tree ``` app/ ├── Actions/ │ └── Fortify/ # Existing - authentication logic │ ├── CreateNewUser.php # Existing │ ├── ResetUserPassword.php # Existing │ └── UpdateUserPassword.php # Existing ├── Http/ │ └── Middleware/ # Create these │ ├── SetLocale.php │ ├── EnsureUserIsAdmin.php │ └── EnsureUserIsActive.php ├── Models/ │ └── User.php # Existing - add helper methods ├── Providers/ │ └── FortifyServiceProvider.php # Existing - configure views bootstrap/ └── app.php # Register middleware aliases config/ ├── fortify.php # Configure features └── session.php # Session configuration resources/ └── views/ └── livewire/ └── auth/ └── login.blade.php # Create - Volt component lang/ ├── ar/ │ └── auth.php # Translation strings └── en/ └── auth.php # Translation strings tests/ ├── Feature/ │ └── Auth/ │ ├── LoginTest.php │ └── AuthorizationTest.php └── Unit/ └── Models/ └── UserHelperMethodsTest.php ``` ### User Model Helper Methods (Add to User.php) ```php // app/Models/User.php use App\Enums\UserType; use App\Enums\UserStatus; public function isAdmin(): bool { return $this->user_type === UserType::Admin; } public function isClient(): bool { return in_array($this->user_type, [UserType::Individual, UserType::Company]); } public function isIndividual(): bool { return $this->user_type === UserType::Individual; } public function isCompany(): bool { return $this->user_type === UserType::Company; } public function isActive(): bool { return $this->status === UserStatus::Active; } ``` ### Middleware Implementation (from Architecture Section 7.3) ```php // app/Http/Middleware/EnsureUserIsAdmin.php class EnsureUserIsAdmin { public function handle(Request $request, Closure $next): Response { if (!$request->user()?->isAdmin()) { abort(403, __('messages.unauthorized')); } return $next($request); } } // app/Http/Middleware/EnsureUserIsActive.php class EnsureUserIsActive { public function handle(Request $request, Closure $next): Response { if ($request->user()?->status === UserStatus::Deactivated) { Auth::logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); return redirect()->route('login') ->with('error', __('auth.account_deactivated')); } return $next($request); } } ``` ### Middleware Registration (bootstrap/app.php) ```php // bootstrap/app.php ->withMiddleware(function (Middleware $middleware) { $middleware->web(append: [ \App\Http\Middleware\SetLocale::class, ]); $middleware->alias([ 'admin' => \App\Http\Middleware\EnsureUserIsAdmin::class, 'active' => \App\Http\Middleware\EnsureUserIsActive::class, ]); }) ``` ### Fortify Configuration ```php // config/fortify.php 'features' => [ // Features::registration(), // DISABLED - admin creates accounts Features::resetPasswords(), Features::emailVerification(), Features::updateProfileInformation(), Features::updatePasswords(), Features::twoFactorAuthentication([ 'confirm' => true, 'confirmPassword' => true, ]), ], ``` ### FortifyServiceProvider Setup ```php // app/Providers/FortifyServiceProvider.php public function boot(): void { // Custom login view Fortify::loginView(fn () => view('livewire.auth.login')); // Custom authentication logic (optional - for deactivated check) Fortify::authenticateUsing(function (Request $request) { $user = User::where('email', $request->email)->first(); if ($user && Hash::check($request->password, $user->password) && $user->status === UserStatus::Active) { return $user; } return null; }); } ``` ### Login Redirect Logic ```php // app/Providers/FortifyServiceProvider.php or custom response // After successful login, redirect based on role: if ($user->isAdmin()) { return redirect('/admin/dashboard'); } return redirect('/client/dashboard'); ``` ### Placeholder Dashboard Routes ```php // routes/web.php Route::middleware(['auth', 'active'])->group(function () { // Admin routes Route::middleware('admin')->prefix('admin')->group(function () { Route::view('/dashboard', 'livewire.admin.dashboard-placeholder') ->name('admin.dashboard'); }); // Client routes Route::prefix('client')->group(function () { Route::view('/dashboard', 'livewire.client.dashboard-placeholder') ->name('client.dashboard'); }); }); ``` ### Environment Configuration ```env SESSION_LIFETIME=120 # 2 hours in minutes SESSION_EXPIRE_ON_CLOSE=false ``` ### Translation Keys (lang/en/auth.php and lang/ar/auth.php) ```php // lang/en/auth.php return [ 'failed' => 'These credentials do not match our records.', 'password' => 'The provided password is incorrect.', 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 'account_deactivated' => 'Your account has been deactivated. Please contact the administrator.', ]; // lang/ar/auth.php return [ 'failed' => 'بيانات الاعتماد هذه لا تتطابق مع سجلاتنا.', 'password' => 'كلمة المرور المقدمة غير صحيحة.', 'throttle' => 'محاولات تسجيل دخول كثيرة جداً. يرجى المحاولة مرة أخرى بعد :seconds ثانية.', 'account_deactivated' => 'تم تعطيل حسابك. يرجى الاتصال بالمسؤول.', ]; ``` ### Edge Case Behavior **Rate Limiting (5 attempts per minute):** - Fortify handles this automatically via `RateLimiter` - After 5 failed attempts, shows throttle message in current locale - Lockout duration: 60 seconds **Session Timeout (2 hours inactivity):** - Configured via `SESSION_LIFETIME=120` - When session expires, redirect to login page - Intended URL preserved for redirect after re-authentication **Deactivated Account Login Attempt:** - Check in `Fortify::authenticateUsing()` callback - Reject with `auth.account_deactivated` message - User never gets authenticated ### Flux UI Components for Login Form ```blade {{ __('Login') }} ``` ## Testing ### Test Location - Feature tests: `tests/Feature/Auth/` - Unit tests: `tests/Unit/Models/` ### Testing Framework Pest 4 with Volt testing support ### Feature Tests (tests/Feature/Auth/LoginTest.php) **Login Flow:** - [ ] `test_login_page_renders_correctly` - Login page displays - [ ] `test_user_can_login_with_valid_credentials` - Valid credentials authenticate - [ ] `test_admin_redirects_to_admin_dashboard` - Admin → `/admin/dashboard` - [ ] `test_client_redirects_to_client_dashboard` - Client → `/client/dashboard` - [ ] `test_invalid_credentials_show_error` - Wrong password shows error - [ ] `test_nonexistent_user_shows_error` - Unknown email shows generic error (no enumeration) - [ ] `test_deactivated_user_cannot_login` - Deactivated account rejected **Rate Limiting:** - [ ] `test_rate_limiting_blocks_after_five_attempts` - 6th attempt blocked - [ ] `test_rate_limit_resets_after_cooldown` - Can retry after 60 seconds **Session Management:** - [ ] `test_logout_clears_session` - Logout destroys session - [ ] `test_authenticated_user_cannot_access_login_page` - Redirect to dashboard ### Feature Tests (tests/Feature/Auth/AuthorizationTest.php) **Middleware:** - [ ] `test_admin_can_access_admin_routes` - Admin passes `admin` middleware - [ ] `test_client_cannot_access_admin_routes` - Client gets 403 - [ ] `test_unauthenticated_user_redirected_to_login` - Guest redirected - [ ] `test_deactivated_user_logged_out_on_request` - Active middleware works ### Unit Tests (tests/Unit/Models/UserHelperMethodsTest.php) **Helper Methods:** - [ ] `test_isAdmin_returns_true_for_admin_user` - [ ] `test_isAdmin_returns_false_for_client_user` - [ ] `test_isClient_returns_true_for_individual_user` - [ ] `test_isClient_returns_true_for_company_user` - [ ] `test_isClient_returns_false_for_admin_user` - [ ] `test_isActive_returns_true_for_active_user` - [ ] `test_isActive_returns_false_for_deactivated_user` ### Test Patterns ```php // tests/Feature/Auth/LoginTest.php use App\Models\User; use App\Enums\UserType; use App\Enums\UserStatus; use Livewire\Volt\Volt; test('admin redirects to admin dashboard after login', function () { $admin = User::factory()->create([ 'user_type' => UserType::Admin, 'status' => UserStatus::Active, ]); $response = $this->post('/login', [ 'email' => $admin->email, 'password' => 'password', ]); $response->assertRedirect('/admin/dashboard'); $this->assertAuthenticatedAs($admin); }); test('deactivated user cannot login', function () { $user = User::factory()->create([ 'status' => UserStatus::Deactivated, ]); $response = $this->post('/login', [ 'email' => $user->email, 'password' => 'password', ]); $this->assertGuest(); }); test('client cannot access admin routes', function () { $client = User::factory()->create([ 'user_type' => UserType::Individual, ]); $this->actingAs($client) ->get('/admin/dashboard') ->assertForbidden(); }); ``` ## Definition of Done - [ ] Login page renders correctly in both languages - [ ] Users can log in with valid credentials - [ ] Invalid credentials show proper error (bilingual) - [ ] Deactivated users cannot log in - [ ] Rate limiting prevents brute force (5 attempts/minute) - [ ] Session expires after 2 hours inactivity - [ ] Admin routes protected by `admin` middleware (403 for clients) - [ ] Failed login attempts logged to `admin_logs` - [ ] All Feature and Unit tests pass - [ ] Code formatted with `vendor/bin/pint` ## Dependencies - **Story 1.1:** Database schema - provides `users` table with `user_type` enum, `status` enum, and `preferred_language` field - **Story 1.3:** Bilingual infrastructure - provides translation files and locale switching (can be developed in parallel) ## Risk Assessment | Risk | Impact | Likelihood | Mitigation | |------|--------|------------|------------| | Security misconfiguration | High | Low | Use Laravel's built-in security features | | Rate limiting bypass | Medium | Low | Use Fortify's built-in rate limiter | | Session hijacking | High | Low | Use secure session configuration | | Middleware registration issues | Low | Medium | Follow architecture Section 7.4 exactly | ### Rollback Plan - Restore default Fortify configuration - Remove custom middleware - Revert User model changes ## Change Log | Date | Version | Description | Author | |------|---------|-------------|--------| | Dec 21, 2025 | 1.0 | Initial story draft | SM Agent | | Dec 21, 2025 | 2.0 | Complete rewrite: Added Status, Tasks/Subtasks, Dev Notes with Source Tree, fixed middleware naming (admin vs can:admin), fixed redirect paths (/client/dashboard), added User helper methods, aligned with architecture Section 7, added Change Log | Validation Task |