libra/docs/stories/story-1.2-authentication-ro...

25 KiB

Story 1.2: Authentication & Role System

Status

Done

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

  1. CSRF protection enabled on all forms
  2. Password hashing using bcrypt
  3. Custom middleware for admin authorization
  4. Secure session configuration
  5. Remember me functionality (optional)

Integration Requirements

  1. Login redirects to appropriate dashboard:
    • Admin users → /admin/dashboard (placeholder until Epic 6)
    • Client users → /client/dashboard (placeholder until Epic 7)
  2. Logout clears session properly and redirects to login page
  3. Admin middleware protects admin-only routes (return 403 for unauthorized)
  4. Failed login attempts logged to admin_logs table
  5. Deactivated users cannot log in

Quality Requirements

  1. Login form validates inputs properly
  2. Error messages are clear and bilingual
  3. All test scenarios in "Testing" section pass
  4. 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)

// 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)

// 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)

// 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

// 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

// 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

// 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

// 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

SESSION_LIFETIME=120    # 2 hours in minutes
SESSION_EXPIRE_ON_CLOSE=false

Translation Keys (lang/en/auth.php and lang/ar/auth.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

<flux:input type="email" wire:model="email" label="{{ __('Email') }}" required />
<flux:input type="password" wire:model="password" label="{{ __('Password') }}" required />
<flux:checkbox wire:model="remember" label="{{ __('Remember me') }}" />
<flux:button type="submit" variant="primary">{{ __('Login') }}</flux:button>

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

// 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

Dev Agent Record

Agent Model Used

Claude Opus 4.5 (claude-opus-4-5-20251101)

File List

New Files:

  • app/Http/Middleware/EnsureUserIsAdmin.php
  • app/Http/Middleware/EnsureUserIsActive.php
  • app/Http/Middleware/SetLocale.php
  • app/Http/Responses/LoginResponse.php
  • app/Http/Responses/VerifyEmailResponse.php
  • app/Listeners/LogFailedLoginAttempt.php
  • lang/en/auth.php
  • lang/ar/auth.php
  • lang/en/messages.php
  • lang/ar/messages.php
  • resources/views/livewire/admin/dashboard-placeholder.blade.php
  • resources/views/livewire/client/dashboard-placeholder.blade.php
  • tests/Feature/Auth/AuthorizationTest.php
  • database/migrations/2025_12_26_115421_make_admin_id_nullable_in_admin_logs_table.php

Modified Files:

  • config/fortify.php - Disabled registration, enabled all other features
  • app/Models/User.php - Added isActive() method
  • app/Providers/FortifyServiceProvider.php - Added custom auth, login response, email verify response
  • app/Providers/AppServiceProvider.php - Registered failed login event listener
  • bootstrap/app.php - Registered middleware aliases (admin, active) and SetLocale
  • routes/web.php - Added admin/client dashboard routes with middleware
  • resources/views/livewire/auth/login.blade.php - Added error message display
  • resources/views/components/layouts/app/sidebar.blade.php - Fixed dashboard route and user name
  • tests/Feature/Auth/AuthenticationTest.php - Updated for role-based redirects
  • tests/Feature/Auth/RegistrationTest.php - Updated for disabled registration
  • tests/Feature/Auth/EmailVerificationTest.php - Updated for role-based redirects
  • tests/Feature/DashboardTest.php - Updated for role-based dashboards
  • tests/Unit/Models/UserTest.php - Added isActive() tests

Completion Notes

  • All 121 tests pass
  • Registration feature disabled per AC 7
  • Role-based redirects implemented (admin → /admin/dashboard, client → /client/dashboard)
  • Custom middleware for admin authorization and active user checking
  • Failed login attempts logged to admin_logs table
  • Bilingual support with SetLocale middleware and translation files
  • Rate limiting configured (5 attempts per minute via Fortify)
  • Session timeout set to 2 hours (SESSION_LIFETIME=120)
  • Note: Created migration to make admin_id nullable in admin_logs table for failed login logging

Future Recommendations (from QA Review)

  • Add verified middleware to dashboard routes: Per architecture.md Section 7.5, routes should use ['auth', 'verified', 'active'] middleware. Currently using ['auth', 'active']. Add verified middleware in routes/web.php:11 when email verification flow is fully implemented and tested in future epics. This ensures only email-verified users can access protected routes.

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
Dec 26, 2025 3.0 Implementation complete: All tasks implemented, 121 tests passing, registration disabled, role-based auth with middleware, failed login logging, bilingual support Dev Agent (James)
Dec 26, 2025 3.1 QA Review PASS: All 21 ACs verified, security review passed, status updated to Done Quinn (Test Architect)

QA Results

Review Date: December 26, 2025

Reviewed By: Quinn (Test Architect)

Risk Assessment

Deep review triggered by:

  • Auth/security files touched (middleware, login, session handling)
  • Story has > 5 acceptance criteria (21 ACs)
  • Security-critical implementation

Code Quality Assessment

The implementation demonstrates high-quality, secure, and well-structured code:

  1. Middleware Implementation - Clean, focused, single-responsibility middleware classes:

    • EnsureUserIsAdmin - Properly checks admin role and returns 403 with translated message
    • EnsureUserIsActive - Correctly logs out deactivated users, invalidates session, regenerates CSRF token
    • SetLocale - Appropriately cascades from user preference → session → config default
  2. User Model Helpers - Well-implemented helper methods:

    • isAdmin(), isClient(), isIndividual(), isCompany(), isActive() - All correctly use enum comparisons
    • Scopes (scopeAdmins, scopeClients, scopeActive) - Properly implemented for query building
  3. Fortify Integration - Properly configured:

    • Registration disabled per AC 7
    • Custom authenticateUsing callback rejects deactivated users at authentication time
    • Custom LoginResponse and VerifyEmailResponse handle role-based redirects
    • Rate limiting configured at 5 attempts per minute
  4. Failed Login Logging - LogFailedLoginAttempt listener correctly logs to admin_logs with IP address

  5. Bilingual Support - Complete translation files for auth.php and messages.php in both en and ar

Requirements Traceability (AC → Test Mapping)

AC# Requirement Test Coverage Status
1 Fortify configured with custom Volt views FortifyServiceProvider.php + manual
2 Login page with bilingual support login.blade.php with __() helpers
3 Session timeout 2 hours SESSION_LIFETIME=120 in .env
4 Rate limiting (5 attempts/min) test_rate_limiting_blocks_after_five_attempts
5 Admin role with full access test_admin_can_access_admin_routes
6 Client role restricted access test_client_cannot_access_admin_routes
7 Registration DISABLED test_registration_is_disabled, test_registration_route_returns_404
8 CSRF protection @csrf in login form + Fortify
9 Password hashing bcrypt Laravel default, 'password' => 'hashed' cast
10 Custom admin middleware EnsureUserIsAdmin.php
11 Secure session configuration Laravel defaults + SESSION_LIFETIME
12 Remember me functionality <flux:checkbox name="remember"> in login
13 Login redirects appropriately test_admin_redirects_to_admin_dashboard, test_client_redirects_to_client_dashboard
14 Logout clears session test_logout_clears_session, test_users_can_logout
15 Admin middleware returns 403 test_client_cannot_access_admin_routes → assertForbidden()
16 Failed login logged to admin_logs test_failed_login_attempts_are_logged
17 Deactivated users cannot login test_deactivated_user_cannot_login, test_deactivated_user_logged_out_on_request
18 Login form validates inputs Fortify built-in validation
19 Error messages bilingual Translation files in lang/en/auth.php, lang/ar/auth.php
20 All test scenarios pass 121 tests passing
21 No security vulnerabilities See Security Review below

Refactoring Performed

No refactoring performed - code quality is already excellent.

Compliance Check

  • Coding Standards: ✓ All files pass vendor/bin/pint --dirty --test
  • Project Structure: ✓ Follows Laravel 12 conventions (middleware in app/Http/Middleware/, registration in bootstrap/app.php)
  • Testing Strategy: ✓ Feature tests for flows, Unit tests for model methods
  • All ACs Met: ✓ All 21 acceptance criteria covered

Improvements Checklist

  • All implementation complete and tested
  • Code formatted with Pint
  • Bilingual translations provided
  • Rate limiting configured
  • Session security configured
  • Consider adding verified middleware to dashboard routes (per architecture.md Section 7.5) - low priority, can be added when email verification flow is fully tested in future epics

Security Review

PASS - No security vulnerabilities found:

  • ✓ CSRF protection enabled (@csrf in forms)
  • ✓ Password hashing using bcrypt (Laravel default)
  • ✓ Rate limiting prevents brute force attacks (5 attempts/minute)
  • ✓ Session regeneration on logout (prevents session fixation)
  • ✓ Deactivated users blocked at authentication and via middleware
  • ✓ No sensitive data exposure in error messages
  • ✓ Generic error message for non-existent users (prevents user enumeration)
  • ✓ Admin routes protected with 403 for unauthorized access
  • ✓ Failed login attempts logged with IP for audit trail

Performance Considerations

PASS - No performance issues:

  • Middleware checks are lightweight (single database field comparisons)
  • No N+1 queries in authentication flow
  • Session-based authentication (no expensive lookups per request)

Files Modified During Review

None - no modifications made during review.

Gate Status

Gate: PASS → docs/qa/gates/1.2-authentication-role-system.yml

✓ Ready for Done - All acceptance criteria met, 121 tests passing, no security issues, code quality excellent.