Files
igny8/docs/00-SYSTEM/AUTH-FLOWS.md
IGNY8 VPS (Salman) c777e5ccb2 dos updates
2026-01-20 14:45:21 +00:00

256 lines
6.7 KiB
Markdown

# Authentication & Authorization
**Last Verified:** January 20, 2026
**Version:** 1.8.4
**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) |
| **Owner** | `owner` | Full access to own account (account creator) |
| **Admin** | `admin` | Full access to own account, billing, team management |
| **Editor** | `editor` | Content creation and editing only |
| **Viewer** | `viewer` | Read-only access to content |
| **System Bot** | `system_bot` | System automation (internal) |
**Role Hierarchy:**
```
developer > owner > admin > 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 |