validated 1.2
This commit is contained in:
parent
ef3f9dfd4e
commit
028ce573b9
|
|
@ -1,91 +1,301 @@
|
|||
# Story 1.2: Authentication & Role System
|
||||
|
||||
## Status
|
||||
Draft
|
||||
|
||||
## Epic Reference
|
||||
**Epic 1:** Core Foundation & Infrastructure
|
||||
|
||||
## User 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**.
|
||||
|
||||
## Story Context
|
||||
|
||||
### Existing System Integration
|
||||
- **Integrates with:** Fortify authentication, users table
|
||||
- **Technology:** Laravel Fortify, Livewire Volt
|
||||
- **Follows pattern:** Existing `app/Actions/Fortify/` for custom logic
|
||||
- **Touch points:** FortifyServiceProvider, login views, middleware
|
||||
## 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
|
||||
- [ ] Fortify configured with custom Volt views
|
||||
- [ ] Login page with bilingual support (Arabic/English)
|
||||
- [ ] Session timeout after 2 hours of inactivity
|
||||
- [ ] Rate limiting on login attempts (5 attempts per minute)
|
||||
- [ ] Admin role with full access to all features
|
||||
- [ ] Client role with restricted access (own data only)
|
||||
- [ ] Registration feature DISABLED (admin creates all accounts)
|
||||
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
|
||||
- [ ] CSRF protection enabled on all forms
|
||||
- [ ] Password hashing using bcrypt
|
||||
- [ ] Gates/Policies for authorization checks
|
||||
- [ ] Secure session configuration
|
||||
- [ ] Remember me functionality (optional)
|
||||
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
|
||||
- [ ] Login redirects to appropriate dashboard:
|
||||
- Admin users → `/admin/dashboard` (to be created in Epic 2)
|
||||
- Client users → `/dashboard` (to be created in Epic 2)
|
||||
- For now, redirect to a placeholder route or home page
|
||||
- [ ] Logout clears session properly and redirects to login page
|
||||
- [ ] Middleware protects admin-only routes (return 403 for unauthorized)
|
||||
- [ ] Failed login attempts logged to `admin_logs` table
|
||||
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
|
||||
- [ ] Login form validates inputs properly
|
||||
- [ ] Error messages are clear and bilingual
|
||||
- [ ] All test scenarios in "Test Scenarios" section pass
|
||||
- [ ] No security vulnerabilities
|
||||
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
|
||||
|
||||
## Technical Notes
|
||||
## 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
|
||||
// Features::registration(), // DISABLED - admin creates accounts
|
||||
Features::resetPasswords(),
|
||||
Features::emailVerification(),
|
||||
Features::updateProfileInformation(),
|
||||
Features::updatePasswords(),
|
||||
Features::twoFactorAuthentication([
|
||||
'confirm' => true,
|
||||
'confirmPassword' => true,
|
||||
]),
|
||||
],
|
||||
```
|
||||
|
||||
### Custom Views Setup
|
||||
### FortifyServiceProvider Setup
|
||||
```php
|
||||
// FortifyServiceProvider boot()
|
||||
Fortify::loginView(fn () => view('auth.login'));
|
||||
// No registerView - registration disabled
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Role Implementation
|
||||
- Use `user_type` column: 'admin', 'individual', 'company'
|
||||
- Admin check: `$user->user_type === 'admin'`
|
||||
- Client check: `in_array($user->user_type, ['individual', 'company'])`
|
||||
|
||||
### Gate Definitions
|
||||
### Login Redirect Logic
|
||||
```php
|
||||
// AuthServiceProvider or AppServiceProvider
|
||||
Gate::define('admin', fn (User $user) => $user->user_type === 'admin');
|
||||
Gate::define('client', fn (User $user) => $user->user_type !== 'admin');
|
||||
// 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');
|
||||
```
|
||||
|
||||
### Middleware
|
||||
- `auth` - Require authentication
|
||||
- `can:admin` - Require admin role
|
||||
- Custom middleware for session timeout if needed
|
||||
### 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
|
||||
|
|
@ -93,56 +303,143 @@ 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):**
|
||||
- After 5 failed attempts, show message: "Too many login attempts. Please try again in 60 seconds."
|
||||
- Use bilingual message from translation files
|
||||
- Log rate limit events for security monitoring
|
||||
- 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
|
||||
- Show flash message: "Your session has expired. Please log in again."
|
||||
- Preserve intended URL for redirect after re-authentication
|
||||
- Intended URL preserved for redirect after re-authentication
|
||||
|
||||
**Deactivated Account Login Attempt:**
|
||||
- Check `status` field during authentication
|
||||
- If `status === 'deactivated'`, reject login with message: "Your account has been deactivated. Please contact the administrator."
|
||||
- Check in `Fortify::authenticateUsing()` callback
|
||||
- Reject with `auth.account_deactivated` message
|
||||
- User never gets authenticated
|
||||
|
||||
## Test Scenarios
|
||||
### Flux UI Components for Login Form
|
||||
```blade
|
||||
<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>
|
||||
```
|
||||
|
||||
### Feature Tests (tests/Feature/Auth/)
|
||||
## 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 in default language
|
||||
- [ ] `test_user_can_login_with_valid_credentials` - Valid credentials redirect to dashboard
|
||||
- [ ] `test_admin_redirects_to_admin_dashboard` - Admin user goes to admin dashboard
|
||||
- [ ] `test_client_redirects_to_client_dashboard` - Client user goes to client dashboard
|
||||
- [ ] `test_invalid_credentials_show_error` - Wrong password shows bilingual error
|
||||
- [ ] `test_nonexistent_user_shows_error` - Unknown email shows generic error (no user enumeration)
|
||||
- [ ] `test_deactivated_user_cannot_login` - Deactivated account rejected with message
|
||||
- [ ] `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_message_is_bilingual` - Error message respects locale
|
||||
- [ ] `test_rate_limit_resets_after_one_minute` - Can retry after cooldown
|
||||
- [ ] `test_rate_limit_resets_after_cooldown` - Can retry after 60 seconds
|
||||
|
||||
**Session Management:**
|
||||
- [ ] `test_logout_clears_session` - Logout destroys session data
|
||||
- [ ] `test_remember_me_extends_session` - Remember token works (if implemented)
|
||||
- [ ] `test_logout_clears_session` - Logout destroys session
|
||||
- [ ] `test_authenticated_user_cannot_access_login_page` - Redirect to dashboard
|
||||
|
||||
**Authorization:**
|
||||
- [ ] `test_admin_can_access_admin_routes` - Admin passes `can:admin` middleware
|
||||
- [ ] `test_client_cannot_access_admin_routes` - Client gets 403 on admin routes
|
||||
- [ ] `test_unauthenticated_user_redirected_to_login` - Guest redirected from protected routes
|
||||
### Feature Tests (tests/Feature/Auth/AuthorizationTest.php)
|
||||
|
||||
### Unit Tests (tests/Unit/)
|
||||
**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
|
||||
|
||||
**Gate Definitions:**
|
||||
- [ ] `test_admin_gate_returns_true_for_admin_user` - Gate check passes
|
||||
- [ ] `test_admin_gate_returns_false_for_client_user` - Gate check fails
|
||||
- [ ] `test_client_gate_returns_true_for_individual_user` - Client gate passes
|
||||
- [ ] `test_client_gate_returns_true_for_company_user` - Client gate passes
|
||||
### 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
|
||||
|
||||
|
|
@ -152,22 +449,33 @@ SESSION_EXPIRE_ON_CLOSE=false
|
|||
- [ ] Deactivated users cannot log in
|
||||
- [ ] Rate limiting prevents brute force (5 attempts/minute)
|
||||
- [ ] Session expires after 2 hours inactivity
|
||||
- [ ] Admin routes protected from clients (403 response)
|
||||
- [ ] All Feature and Unit tests from "Test Scenarios" section pass
|
||||
- [ ] Code formatted with Pint
|
||||
- [ ] 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 - creates `users` table with `user_type` enum (`admin`, `individual`, `company`), `status` enum (`active`, `deactivated`), and `preferred_language` enum (`ar`, `en`)
|
||||
- **Story 1.3:** Bilingual infrastructure (for login page translations)
|
||||
- **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
|
||||
|
||||
- **Primary Risk:** Security misconfiguration
|
||||
- **Mitigation:** Use Laravel's built-in security features, no custom auth logic
|
||||
- **Rollback:** Restore Fortify defaults
|
||||
| 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 |
|
||||
|
||||
## Estimation
|
||||
### Rollback Plan
|
||||
- Restore default Fortify configuration
|
||||
- Remove custom middleware
|
||||
- Revert User model changes
|
||||
|
||||
**Complexity:** Medium
|
||||
**Estimated Effort:** 3-4 hours
|
||||
## 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 |
|
||||
|
|
|
|||
Loading…
Reference in New Issue