# Authentication & Authorization

**Last Verified:** December 25, 2025  
**Backend Path:** `backend/igny8_core/auth/`  
**Frontend Path:** `frontend/src/store/authStore.ts`

---

## Quick Reference

| What | File | Key Functions |
|------|------|---------------|
| User Model | `auth/models.py` | `User`, `Account`, `Plan` |
| Auth Views | `auth/views.py` | `LoginView`, `RegisterView`, `RefreshTokenView` |
| Middleware | `auth/middleware.py` | `AccountContextMiddleware` |
| JWT Auth | `api/authentication.py` | `JWTAuthentication`, `CookieJWTAuthentication` |
| API Key Auth | `api/authentication.py` | `APIKeyAuthentication` |
| Frontend Store | `store/authStore.ts` | `useAuthStore` |

---

## Authentication Methods

### 1. JWT Token Authentication (Primary)

**Flow:**
1. User logs in via `/api/v1/auth/login/`
2. Backend returns `access_token` (15 min) + `refresh_token` (7 days)
3. Frontend stores tokens in localStorage and Zustand store
4. All API requests include `Authorization: Bearer <access_token>`
5. Token refresh via `/api/v1/auth/token/refresh/`

**Token Payload:**
```json
{
  "user_id": 123,
  "account_id": 456,
  "email": "user@example.com",
  "exp": 1735123456,
  "iat": 1735122456
}
```

### 2. Session Authentication (Admin/Fallback)

- Used by Django Admin interface
- Cookie-based session with CSRF protection
- Redis-backed sessions (prevents user swapping bug)

### 3. API Key Authentication (WordPress Bridge)

**Flow:**
1. Account generates API key in settings
2. WordPress plugin uses `Authorization: ApiKey <key>`
3. Backend validates key, sets `request.account` and `request.site`

**Use Cases:**
- WordPress content sync
- External integrations
- Headless CMS connections

---

## API Endpoints

| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| POST | `/api/v1/auth/register/` | `RegisterView` | Create new user + account |
| POST | `/api/v1/auth/login/` | `LoginView` | Authenticate, return tokens |
| POST | `/api/v1/auth/logout/` | `LogoutView` | Invalidate tokens |
| POST | `/api/v1/auth/token/refresh/` | `RefreshTokenView` | Refresh access token |
| POST | `/api/v1/auth/password/change/` | `ChangePasswordView` | Change password |
| POST | `/api/v1/auth/password/reset/` | `RequestPasswordResetView` | Request reset email |
| POST | `/api/v1/auth/password/reset/confirm/` | `ResetPasswordView` | Confirm reset with token |

---

## User Roles

| Role | Code | Permissions |
|------|------|-------------|
| **Developer** | `developer` | Full access across ALL accounts (superuser) |
| **Admin** | `admin` | Full access to own account |
| **Manager** | `manager` | Manage content, view billing |
| **Editor** | `editor` | AI content, manage clusters/tasks |
| **Viewer** | `viewer` | Read-only dashboards |
| **System Bot** | `system_bot` | System automation (internal) |

**Role Hierarchy:**
```
developer > admin > manager > editor > viewer
```

---

## Middleware: AccountContextMiddleware

**File:** `auth/middleware.py`

**Purpose:** Injects `request.account` on every request

**Flow:**
1. Check for JWT token → extract account_id
2. Check for session → get account from session
3. Check for API key → get account from key
4. Validate account exists and is active
5. Validate plan exists and is active
6. Set `request.account`, `request.user`

**Error Responses:**
- No account: 403 with JSON error
- Inactive plan: 402 with JSON error

---

## Frontend Auth Store

**File:** `store/authStore.ts`

**State:**
```typescript
{
  user: User | null;
  token: string | null;
  refreshToken: string | null;
  isAuthenticated: boolean;
}
```

**Actions:**
- `login(email, password)` - Authenticate and store tokens
- `register(data)` - Create account and store tokens
- `logout()` - Clear tokens and reset stores
- `refreshToken()` - Refresh access token
- `checkAuth()` - Verify current auth state

**Critical Implementation:**
```typescript
// Tokens are written synchronously to localStorage
// This prevents race conditions where API calls happen before persist
localStorage.setItem('auth-storage', JSON.stringify(authState));
```

---

## Session Security (Redis-Backed)

**Problem Solved:** User swapping / random logout issues

**Implementation:**
```python
# settings.py
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'  # Redis

# auth/backends.py
class NoCacheModelBackend(ModelBackend):
    """Authentication backend without user caching"""
    pass
```

**Session Integrity:**
- Stores `account_id` and `user_id` in session
- Validates on every request
- Prevents cross-request contamination

---

## API Key Management

**Model:** `APIKey` in `auth/models.py`

| Field | Type | Purpose |
|-------|------|---------|
| key | CharField | Hashed API key |
| account | ForeignKey | Owner account |
| site | ForeignKey | Optional: specific site |
| name | CharField | Key name/description |
| is_active | Boolean | Enable/disable |
| created_at | DateTime | Creation time |
| last_used_at | DateTime | Last usage time |

**Generation:**
- 32-character random key
- Stored hashed (SHA-256)
- Shown once on creation

---

## Permission Checking

**In ViewSets:**
```python
class MyViewSet(AccountModelViewSet):
    permission_classes = [IsAuthenticated]
    
    def get_queryset(self):
        # Automatically filtered by request.account
        return super().get_queryset()
```

**Role Checks:**
```python
if request.user.is_admin_or_developer:
    # Admin/developer access
    pass
elif request.user.role == 'editor':
    # Editor access
    pass
```

---

## Logout Flow

**Backend:**
1. Blacklist refresh token (if using token blacklist)
2. Clear session

**Frontend (Critical):**
```typescript
logout: () => {
  // NEVER use localStorage.clear() - breaks Zustand persist
  const authKeys = ['auth-storage', 'site-storage', 'sector-storage', 'billing-storage'];
  authKeys.forEach(key => localStorage.removeItem(key));
  
  // Reset dependent stores
  useSiteStore.setState({ activeSite: null });
  useSectorStore.setState({ activeSector: null, sectors: [] });
  
  set({ user: null, token: null, isAuthenticated: false });
}
```

---

## Common Issues

| Issue | Cause | Fix |
|-------|-------|-----|
| 403 after login | Tokens not persisted before API call | Write to localStorage synchronously |
| User swapping | DB-backed sessions with user caching | Redis sessions + NoCacheModelBackend |
| Token refresh loop | Refresh token expired | Redirect to login |
| API key not working | Missing site scope | Check API key has correct site assigned |

---

## Planned Changes

| Feature | Status | Description |
|---------|--------|-------------|
| Token blacklist | 🔜 Planned | Proper refresh token invalidation |
| 2FA | 🔜 Planned | Two-factor authentication |
| SSO | 🔜 Planned | Google/GitHub OAuth |
