1358 lines
41 KiB
Markdown
1358 lines
41 KiB
Markdown
# API Implementation Plan - Section 2: Standardize Authentication and Authorization
|
|
|
|
**Date:** 2025-01-XX
|
|
**Status:** Planning
|
|
**Priority:** High
|
|
**Related Document:** `API-ENDPOINTS-ANALYSIS.md`
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
This document outlines the implementation plan for **Section 2: Standardize Authentication and Authorization** across the IGNY8 API layer. The goal is to ensure every API endpoint enforces consistent access control based on authenticated users, roles, and tenant/site scoping, improving security and maintainability.
|
|
|
|
**Key Objectives:**
|
|
- Standardize permission classes across all endpoints
|
|
- Implement consistent tenant/site-based access control
|
|
- Add role-based authorization where needed
|
|
- Secure custom actions and endpoints
|
|
- Ensure proper authentication fallback mechanisms
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Current State Analysis](#current-state-analysis)
|
|
2. [Implementation Tasks](#implementation-tasks)
|
|
3. [Task 1: Define Global Permission Classes](#task-1-define-global-permission-classes)
|
|
4. [Task 2: Create Unified BaseViewSet with Permission Injection](#task-2-create-unified-baseviewset-with-permission-injection)
|
|
5. [Task 3: Audit and Refactor All ViewSets](#task-3-audit-and-refactor-all-viewsets)
|
|
6. [Task 4: Inject Role-Based Checks Where Needed](#task-4-inject-role-based-checks-where-needed)
|
|
7. [Task 5: Secure Custom Actions](#task-5-secure-custom-actions)
|
|
8. [Task 6: Validate Token and Session Auth Coexistence](#task-6-validate-token-and-session-auth-coexistence)
|
|
9. [Task 7: Frontend Sync + Fallback UX](#task-7-frontend-sync--fallback-ux)
|
|
10. [Task 8: Changelog Entry](#task-8-changelog-entry)
|
|
11. [Testing Strategy](#testing-strategy)
|
|
12. [Rollout Plan](#rollout-plan)
|
|
13. [Success Criteria](#success-criteria)
|
|
|
|
---
|
|
|
|
## Current State Analysis
|
|
|
|
### Current Authentication & Authorization Issues
|
|
|
|
Based on `API-ENDPOINTS-ANALYSIS.md`, the following inconsistencies exist:
|
|
|
|
1. **Inconsistent Permission Classes:**
|
|
- Default: `AllowAny` (most endpoints)
|
|
- Many ViewSets set `permission_classes = []` (explicit AllowAny)
|
|
- Some use `IsAuthenticated`
|
|
- Some use custom classes: `IsOwnerOrAdmin`, `IsEditorOrAbove`
|
|
- No consistent pattern across modules
|
|
|
|
2. **Existing Permission Classes:**
|
|
- `IsAuthenticated` - DRF standard
|
|
- `IsOwnerOrAdmin` - Custom (owner or admin role)
|
|
- `IsEditorOrAbove` - Custom (editor, admin, or owner)
|
|
- `IsViewerOrAbove` - Custom (viewer or above)
|
|
- `AccountPermission` - Custom (account-based access)
|
|
|
|
3. **Base Classes:**
|
|
- `AccountModelViewSet` - Has account filtering but no default permissions
|
|
- `SiteSectorModelViewSet` - Has site/sector filtering but no default permissions
|
|
|
|
4. **Authentication Methods:**
|
|
- JWT Authentication (primary) - `igny8_core.api.authentication.JWTAuthentication`
|
|
- Session Authentication (fallback) - `CSRFExemptSessionAuthentication`
|
|
- Basic Authentication (fallback) - `rest_framework.authentication.BasicAuthentication`
|
|
|
|
5. **Account Context:**
|
|
- `AccountContextMiddleware` sets `request.account` from JWT token
|
|
- Account filtering happens in ViewSets but not consistently enforced
|
|
|
|
---
|
|
|
|
## Implementation Tasks
|
|
|
|
### Overview
|
|
|
|
| Task ID | Task Name | Priority | Estimated Effort | Dependencies |
|
|
|---------|-----------|----------|------------------|--------------|
|
|
| 1.1 | Create global permission classes | High | 3 hours | None |
|
|
| 2.1 | Create BaseTenantViewSet | High | 4 hours | 1.1 |
|
|
| 3.1 | Audit all ViewSets | Medium | 4 hours | None |
|
|
| 3.2 | Refactor Auth module ViewSets | High | 6 hours | 2.1 |
|
|
| 3.3 | Refactor Planner module ViewSets | High | 6 hours | 2.1 |
|
|
| 3.4 | Refactor Writer module ViewSets | High | 6 hours | 2.1 |
|
|
| 3.5 | Refactor System module ViewSets | High | 8 hours | 2.1 |
|
|
| 3.6 | Refactor Billing module ViewSets | High | 4 hours | 2.1 |
|
|
| 4.1 | Define role system | High | 2 hours | None |
|
|
| 4.2 | Create role-based permission classes | High | 4 hours | 4.1 |
|
|
| 4.3 | Apply role checks to elevated endpoints | High | 6 hours | 4.2 |
|
|
| 5.1 | Audit all custom actions | Medium | 4 hours | None |
|
|
| 5.2 | Secure custom actions | High | 8 hours | 1.1, 2.1 |
|
|
| 6.1 | Validate auth coexistence | High | 3 hours | None |
|
|
| 7.1 | Update frontend error handling | High | 4 hours | None |
|
|
| 7.2 | Update frontend role storage | Medium | 2 hours | None |
|
|
| 7.3 | Add UI guards | Medium | 4 hours | 7.2 |
|
|
| 8.1 | Create changelog entry | Low | 1 hour | All tasks |
|
|
|
|
**Total Estimated Effort:** ~75 hours
|
|
|
|
---
|
|
|
|
## Task 1: Define Global Permission Classes
|
|
|
|
### Goal
|
|
Create reusable permission classes that enforce consistent access control across all endpoints.
|
|
|
|
### Implementation Steps
|
|
|
|
#### Step 1.1: Create Permission Classes Module
|
|
|
|
**File:** `backend/igny8_core/api/permissions.py`
|
|
|
|
**Implementation:**
|
|
```python
|
|
"""
|
|
Unified Permission Classes for IGNY8 API
|
|
|
|
This module provides permission classes that enforce consistent
|
|
access control based on authentication, tenant access, and roles.
|
|
"""
|
|
|
|
from rest_framework import permissions
|
|
from rest_framework.exceptions import PermissionDenied
|
|
|
|
|
|
class IsAuthenticatedAndActive(permissions.BasePermission):
|
|
"""
|
|
Permission class that requires user to be authenticated and active.
|
|
|
|
This is the base permission for most endpoints. It ensures:
|
|
- User is authenticated
|
|
- User account is active
|
|
- User is not disabled
|
|
"""
|
|
|
|
def has_permission(self, request, view):
|
|
"""
|
|
Check if user is authenticated and active.
|
|
"""
|
|
if not request.user:
|
|
return False
|
|
|
|
if not request.user.is_authenticated:
|
|
return False
|
|
|
|
if not request.user.is_active:
|
|
return False
|
|
|
|
return True
|
|
|
|
def has_object_permission(self, request, view, obj):
|
|
"""
|
|
Object-level permission check.
|
|
By default, if user passes has_permission, allow object access.
|
|
Override in subclasses for object-level checks.
|
|
"""
|
|
return self.has_permission(request, view)
|
|
|
|
|
|
class HasTenantAccess(permissions.BasePermission):
|
|
"""
|
|
Permission class that ensures user has access to the tenant (account).
|
|
|
|
This permission:
|
|
- Requires user to be authenticated
|
|
- Checks that user belongs to the account (request.account)
|
|
- Allows system bots to bypass checks
|
|
- Validates account context is set
|
|
"""
|
|
|
|
def has_permission(self, request, view):
|
|
"""
|
|
Check if user has access to the tenant/account.
|
|
"""
|
|
if not request.user or not request.user.is_authenticated:
|
|
return False
|
|
|
|
# System bots can access all accounts
|
|
if hasattr(request.user, 'role') and request.user.role == 'system_bot':
|
|
return True
|
|
|
|
# Check if account context is set (from middleware or token)
|
|
account = getattr(request, 'account', None)
|
|
if not account:
|
|
# No account context - deny access
|
|
return False
|
|
|
|
# Check if user belongs to this account
|
|
user_account = getattr(request.user, 'account', None)
|
|
if not user_account:
|
|
return False
|
|
|
|
# User must belong to the account
|
|
return user_account == account
|
|
|
|
def has_object_permission(self, request, view, obj):
|
|
"""
|
|
Object-level permission: check if object belongs to user's account.
|
|
"""
|
|
if not request.user or not request.user.is_authenticated:
|
|
return False
|
|
|
|
# System bots can access all
|
|
if hasattr(request.user, 'role') and request.user.role == 'system_bot':
|
|
return True
|
|
|
|
# Check if object has account field
|
|
obj_account = getattr(obj, 'account', None)
|
|
if not obj_account:
|
|
# Object doesn't have account - allow (for non-account models)
|
|
return True
|
|
|
|
# Get user's account
|
|
user_account = getattr(request.user, 'account', None)
|
|
if not user_account:
|
|
return False
|
|
|
|
# Object must belong to user's account
|
|
return obj_account == user_account
|
|
|
|
|
|
class HasModuleAccess(permissions.BasePermission):
|
|
"""
|
|
Permission class that checks if user has access to a specific module.
|
|
|
|
This is optional and can be used for module-level access control
|
|
(e.g., Planner, Writer, System, Billing modules).
|
|
|
|
Usage:
|
|
permission_classes = [IsAuthenticatedAndActive, HasTenantAccess, HasModuleAccess]
|
|
# Then set module_name in ViewSet: module_name = 'planner'
|
|
"""
|
|
|
|
def has_permission(self, request, view):
|
|
"""
|
|
Check if user has access to the module.
|
|
"""
|
|
if not request.user or not request.user.is_authenticated:
|
|
return False
|
|
|
|
# Get module name from view
|
|
module_name = getattr(view, 'module_name', None)
|
|
if not module_name:
|
|
# No module restriction - allow
|
|
return True
|
|
|
|
# Check if user's account has access to this module
|
|
# This can be based on subscription, plan, or account settings
|
|
account = getattr(request, 'account', None)
|
|
if not account:
|
|
return False
|
|
|
|
# TODO: Implement module access check based on account settings
|
|
# For now, allow all authenticated users
|
|
# Example: return account.has_module_access(module_name)
|
|
return True
|
|
|
|
|
|
class IsAdminOrOwner(permissions.BasePermission):
|
|
"""
|
|
Permission class that requires user to have admin or owner role.
|
|
|
|
Use this for elevated privileges like:
|
|
- Credit management
|
|
- API key management
|
|
- User administration
|
|
- Account settings
|
|
"""
|
|
|
|
def has_permission(self, request, view):
|
|
"""
|
|
Check if user has admin or owner role.
|
|
"""
|
|
if not request.user or not request.user.is_authenticated:
|
|
return False
|
|
|
|
# Check user role
|
|
user_role = getattr(request.user, 'role', None)
|
|
if not user_role:
|
|
return False
|
|
|
|
# Allow admin or owner
|
|
return user_role in ['admin', 'owner']
|
|
|
|
def has_object_permission(self, request, view, obj):
|
|
"""
|
|
Object-level check: user must be admin or owner.
|
|
"""
|
|
return self.has_permission(request, view)
|
|
|
|
|
|
class IsEditorOrAbove(permissions.BasePermission):
|
|
"""
|
|
Permission class that requires editor, admin, or owner role.
|
|
|
|
Use this for content management operations.
|
|
"""
|
|
|
|
def has_permission(self, request, view):
|
|
"""
|
|
Check if user has editor, admin, or owner role.
|
|
"""
|
|
if not request.user or not request.user.is_authenticated:
|
|
return False
|
|
|
|
user_role = getattr(request.user, 'role', None)
|
|
if not user_role:
|
|
return False
|
|
|
|
# Allow editor, admin, or owner
|
|
return user_role in ['editor', 'admin', 'owner']
|
|
|
|
def has_object_permission(self, request, view, obj):
|
|
"""
|
|
Object-level check: user must be editor or above.
|
|
"""
|
|
return self.has_permission(request, view)
|
|
|
|
|
|
class IsViewerOrAbove(permissions.BasePermission):
|
|
"""
|
|
Permission class that requires viewer, editor, admin, or owner role.
|
|
|
|
Use this for read-only operations that should be accessible to all roles.
|
|
"""
|
|
|
|
def has_permission(self, request, view):
|
|
"""
|
|
Check if user has viewer, editor, admin, or owner role.
|
|
"""
|
|
if not request.user or not request.user.is_authenticated:
|
|
return False
|
|
|
|
user_role = getattr(request.user, 'role', None)
|
|
if not user_role:
|
|
return False
|
|
|
|
# Allow viewer, editor, admin, or owner
|
|
return user_role in ['viewer', 'editor', 'admin', 'owner']
|
|
|
|
def has_object_permission(self, request, view, obj):
|
|
"""
|
|
Object-level check: user must be viewer or above.
|
|
"""
|
|
return self.has_permission(request, view)
|
|
```
|
|
|
|
#### Step 1.2: Update `__init__.py` for Easy Import
|
|
|
|
**File:** `backend/igny8_core/api/__init__.py`
|
|
|
|
**Add:**
|
|
```python
|
|
from .permissions import (
|
|
IsAuthenticatedAndActive,
|
|
HasTenantAccess,
|
|
HasModuleAccess,
|
|
IsAdminOrOwner,
|
|
IsEditorOrAbove,
|
|
IsViewerOrAbove,
|
|
)
|
|
|
|
__all__ = [
|
|
'IsAuthenticatedAndActive',
|
|
'HasTenantAccess',
|
|
'HasModuleAccess',
|
|
'IsAdminOrOwner',
|
|
'IsEditorOrAbove',
|
|
'IsViewerOrAbove',
|
|
]
|
|
```
|
|
|
|
#### Step 1.3: Create Unit Tests
|
|
|
|
**File:** `backend/igny8_core/api/tests/test_permissions.py`
|
|
|
|
**Test Cases:**
|
|
- Test `IsAuthenticatedAndActive` with authenticated/active user
|
|
- Test `IsAuthenticatedAndActive` with unauthenticated user
|
|
- Test `IsAuthenticatedAndActive` with inactive user
|
|
- Test `HasTenantAccess` with matching account
|
|
- Test `HasTenantAccess` with mismatched account
|
|
- Test `HasTenantAccess` with system bot
|
|
- Test `IsAdminOrOwner` with admin role
|
|
- Test `IsAdminOrOwner` with owner role
|
|
- Test `IsAdminOrOwner` with editor role (should fail)
|
|
- Test `IsEditorOrAbove` with various roles
|
|
- Test object-level permissions
|
|
|
|
**Estimated Time:** 3 hours
|
|
|
|
---
|
|
|
|
## Task 2: Create Unified BaseViewSet with Permission Injection
|
|
|
|
### Goal
|
|
Create a base ViewSet class that automatically applies standard permissions and tenant filtering.
|
|
|
|
### Implementation Steps
|
|
|
|
#### Step 2.1: Create BaseTenantViewSet
|
|
|
|
**File:** `backend/igny8_core/api/viewsets.py`
|
|
|
|
**Implementation:**
|
|
```python
|
|
"""
|
|
Base ViewSet Classes with Unified Permissions and Tenant Filtering
|
|
"""
|
|
|
|
from rest_framework import viewsets
|
|
from igny8_core.api.permissions import (
|
|
IsAuthenticatedAndActive,
|
|
HasTenantAccess,
|
|
)
|
|
from igny8_core.api.base import AccountModelViewSet
|
|
|
|
|
|
class BaseTenantViewSet(AccountModelViewSet):
|
|
"""
|
|
Base ViewSet that automatically applies standard permissions and tenant filtering.
|
|
|
|
This ViewSet:
|
|
- Requires authentication and active user
|
|
- Enforces tenant/account access
|
|
- Automatically filters queryset by account
|
|
- Can be extended for module-specific or role-specific access
|
|
|
|
All module ViewSets should inherit from this instead of AccountModelViewSet.
|
|
|
|
Usage:
|
|
class MyViewSet(BaseTenantViewSet):
|
|
queryset = MyModel.objects.all()
|
|
serializer_class = MySerializer
|
|
"""
|
|
|
|
# Default permissions: require authentication and tenant access
|
|
permission_classes = [
|
|
IsAuthenticatedAndActive,
|
|
HasTenantAccess,
|
|
]
|
|
|
|
def get_queryset(self):
|
|
"""
|
|
Get queryset filtered by account.
|
|
|
|
This extends AccountModelViewSet.get_queryset() to ensure
|
|
proper account filtering is applied.
|
|
"""
|
|
queryset = super().get_queryset()
|
|
|
|
# Account filtering is handled by AccountModelViewSet
|
|
# This method can be overridden in subclasses for additional filtering
|
|
|
|
return queryset
|
|
|
|
def perform_create(self, serializer):
|
|
"""
|
|
Override to ensure account is set on create.
|
|
"""
|
|
# Get account from request (set by middleware or authentication)
|
|
account = getattr(self.request, 'account', None)
|
|
|
|
if account and hasattr(serializer.Meta.model, 'account'):
|
|
# Set account on the object being created
|
|
serializer.save(account=account)
|
|
else:
|
|
serializer.save()
|
|
```
|
|
|
|
#### Step 2.2: Update AccountModelViewSet (if needed)
|
|
|
|
**File:** `backend/igny8_core/api/base.py`
|
|
|
|
**Review and ensure:**
|
|
- Account filtering logic is correct
|
|
- Admin/developer override logic works
|
|
- System account bypass works correctly
|
|
- Compatible with new permission classes
|
|
|
|
**Estimated Time:** 4 hours
|
|
|
|
---
|
|
|
|
## Task 3: Audit and Refactor All ViewSets
|
|
|
|
### Goal
|
|
Update all ViewSets to inherit from `BaseTenantViewSet` and remove redundant permission logic.
|
|
|
|
### Implementation Strategy
|
|
|
|
#### Step 3.1: Audit All ViewSets
|
|
|
|
**Action Items:**
|
|
1. List all ViewSets in each module
|
|
2. Document current permission classes
|
|
3. Identify ViewSets that need role-based permissions
|
|
4. Identify ViewSets that should remain public (AllowAny)
|
|
5. Create refactoring checklist
|
|
|
|
**Files to Audit:**
|
|
|
|
**Auth Module:**
|
|
- `backend/auth/api/views.py` - UsersViewSet, AccountsViewSet, SiteViewSet, etc.
|
|
- `backend/auth/api/viewsets.py` - Custom authentication views
|
|
|
|
**Planner Module:**
|
|
- `backend/planner/api/viewsets.py` - KeywordViewSet, ClusterViewSet, ContentIdeasViewSet
|
|
|
|
**Writer Module:**
|
|
- `backend/writer/api/viewsets.py` - TasksViewSet, ContentViewSet, ImagesViewSet
|
|
|
|
**System Module:**
|
|
- `backend/system/api/viewsets.py` - AIPromptViewSet, AuthorProfileViewSet, StrategyViewSet
|
|
- `backend/system/api/viewsets.py` - IntegrationSettingsViewSet, SystemSettingsViewSet, etc.
|
|
|
|
**Billing Module:**
|
|
- `backend/billing/api/viewsets.py` - CreditBalanceViewSet, CreditUsageViewSet, CreditTransactionViewSet
|
|
|
|
**Public Endpoints (should remain AllowAny):**
|
|
- `POST /api/v1/auth/register/` - Registration
|
|
- `POST /api/v1/auth/login/` - Login
|
|
- `GET /api/v1/auth/plans/` - Public plans list
|
|
- `GET /api/v1/auth/industries/` - Public industries list
|
|
- `GET /api/v1/system/status/` - System health check
|
|
|
|
**Estimated Time:** 4 hours
|
|
|
|
#### Step 3.2: Refactor Auth Module ViewSets
|
|
|
|
**Priority Endpoints:**
|
|
1. `POST /api/v1/auth/register/` - Keep `AllowAny` (public)
|
|
2. `POST /api/v1/auth/login/` - Keep `AllowAny` (public)
|
|
3. `POST /api/v1/auth/change-password/` - Use `IsAuthenticatedAndActive`
|
|
4. `GET /api/v1/auth/me/` - Use `IsAuthenticatedAndActive`
|
|
5. UsersViewSet - Use `IsAdminOrOwner` (user management)
|
|
6. AccountsViewSet - Use `IsAdminOrOwner` (account management)
|
|
7. SiteViewSet - Use `IsEditorOrAbove` (site management)
|
|
8. SectorViewSet - Use `IsEditorOrAbove` (sector management)
|
|
9. PlanViewSet - Keep `AllowAny` (public read-only)
|
|
10. IndustryViewSet - Keep `AllowAny` (public read-only)
|
|
|
|
**Example Refactoring:**
|
|
|
|
**Before:**
|
|
```python
|
|
class UsersViewSet(AccountModelViewSet):
|
|
permission_classes = [IsOwnerOrAdmin] # Old custom class
|
|
queryset = User.objects.all()
|
|
serializer_class = UserSerializer
|
|
```
|
|
|
|
**After:**
|
|
```python
|
|
from igny8_core.api.viewsets import BaseTenantViewSet
|
|
from igny8_core.api.permissions import IsAdminOrOwner
|
|
|
|
class UsersViewSet(BaseTenantViewSet):
|
|
permission_classes = [IsAuthenticatedAndActive, HasTenantAccess, IsAdminOrOwner]
|
|
queryset = User.objects.all()
|
|
serializer_class = UserSerializer
|
|
```
|
|
|
|
**Estimated Time:** 6 hours
|
|
|
|
#### Step 3.3: Refactor Planner Module ViewSets
|
|
|
|
**Priority Endpoints:**
|
|
1. KeywordViewSet - Use `IsAuthenticatedAndActive, HasTenantAccess`
|
|
2. ClusterViewSet - Use `IsAuthenticatedAndActive, HasTenantAccess`
|
|
3. ContentIdeasViewSet - Use `IsAuthenticatedAndActive, HasTenantAccess`
|
|
|
|
**Note:** Planner module ViewSets inherit from `SiteSectorModelViewSet`, which already has account filtering. Update to use `BaseTenantViewSet` or create `BaseSiteSectorViewSet` that extends `BaseTenantViewSet`.
|
|
|
|
**Estimated Time:** 6 hours
|
|
|
|
#### Step 3.4: Refactor Writer Module ViewSets
|
|
|
|
**Priority Endpoints:**
|
|
1. TasksViewSet - Use `IsAuthenticatedAndActive, HasTenantAccess`
|
|
2. ContentViewSet - Use `IsAuthenticatedAndActive, HasTenantAccess`
|
|
3. ImagesViewSet - Use `IsAuthenticatedAndActive, HasTenantAccess`
|
|
|
|
**Estimated Time:** 6 hours
|
|
|
|
#### Step 3.5: Refactor System Module ViewSets
|
|
|
|
**Priority Endpoints:**
|
|
1. AIPromptViewSet - Use `IsAuthenticatedAndActive, HasTenantAccess`
|
|
2. AuthorProfileViewSet - Use `IsAuthenticatedAndActive, HasTenantAccess`
|
|
3. StrategyViewSet - Use `IsAuthenticatedAndActive, HasTenantAccess`
|
|
4. IntegrationSettingsViewSet - Use `IsAdminOrOwner` (sensitive settings)
|
|
5. SystemSettingsViewSet - Use `IsAdminOrOwner` (system settings)
|
|
6. AccountSettingsViewSet - Use `IsAuthenticatedAndActive, HasTenantAccess`
|
|
7. UserSettingsViewSet - Use `IsAuthenticatedAndActive` (user-specific)
|
|
|
|
**Estimated Time:** 8 hours
|
|
|
|
#### Step 3.6: Refactor Billing Module ViewSets
|
|
|
|
**Priority Endpoints:**
|
|
1. CreditBalanceViewSet - Use `IsAuthenticatedAndActive, HasTenantAccess`
|
|
2. CreditUsageViewSet - Use `IsAuthenticatedAndActive, HasTenantAccess`
|
|
3. CreditTransactionViewSet - Use `IsAdminOrOwner` (sensitive financial data)
|
|
|
|
**Estimated Time:** 4 hours
|
|
|
|
### Refactoring Checklist Template
|
|
|
|
For each ViewSet, check:
|
|
|
|
- [ ] Inherit from `BaseTenantViewSet` (or appropriate base class)
|
|
- [ ] Set appropriate `permission_classes`
|
|
- [ ] Remove redundant permission logic
|
|
- [ ] Ensure queryset filtering works correctly
|
|
- [ ] Test authentication required
|
|
- [ ] Test tenant access enforcement
|
|
- [ ] Test role-based access (if applicable)
|
|
- [ ] Verify public endpoints remain public (AllowAny)
|
|
|
|
---
|
|
|
|
## Task 4: Inject Role-Based Checks Where Needed
|
|
|
|
### Goal
|
|
Implement role-based authorization for endpoints that require elevated privileges.
|
|
|
|
### Implementation Steps
|
|
|
|
#### Step 4.1: Define Role System
|
|
|
|
**Review existing role system:**
|
|
|
|
**File:** `backend/igny8_core/auth/models.py` (or wherever User model is defined)
|
|
|
|
**Roles to support:**
|
|
- `owner` - Full account access
|
|
- `admin` - Administrative access
|
|
- `editor` - Content editing access
|
|
- `writer` - Content writing access
|
|
- `viewer` - Read-only access
|
|
- `system_bot` - System/internal access
|
|
|
|
**Action Items:**
|
|
1. Verify User model has `role` field
|
|
2. Document role hierarchy and permissions
|
|
3. Create role enum or constants if needed
|
|
|
|
**Estimated Time:** 2 hours
|
|
|
|
#### Step 4.2: Create Role-Based Permission Classes
|
|
|
|
**File:** `backend/igny8_core/api/permissions.py` (add to existing file)
|
|
|
|
**Additional Permission Classes:**
|
|
```python
|
|
class RequireRole(permissions.BasePermission):
|
|
"""
|
|
Permission class that requires a specific role or set of roles.
|
|
|
|
Usage:
|
|
permission_classes = [IsAuthenticatedAndActive, RequireRole(['admin', 'owner'])]
|
|
"""
|
|
|
|
def __init__(self, allowed_roles):
|
|
self.allowed_roles = allowed_roles if isinstance(allowed_roles, list) else [allowed_roles]
|
|
|
|
def has_permission(self, request, view):
|
|
if not request.user or not request.user.is_authenticated:
|
|
return False
|
|
|
|
user_role = getattr(request.user, 'role', None)
|
|
if not user_role:
|
|
return False
|
|
|
|
return user_role in self.allowed_roles
|
|
|
|
|
|
class RolePermissionMixin:
|
|
"""
|
|
Mixin that adds role-based permission checking to ViewSets.
|
|
|
|
Usage:
|
|
class MyViewSet(BaseTenantViewSet, RolePermissionMixin):
|
|
required_roles = ['admin', 'owner']
|
|
"""
|
|
|
|
def get_permissions(self):
|
|
"""
|
|
Add role-based permission if required_roles is set.
|
|
"""
|
|
permissions = super().get_permissions()
|
|
|
|
required_roles = getattr(self, 'required_roles', None)
|
|
if required_roles:
|
|
permissions.append(RequireRole(required_roles))
|
|
|
|
return permissions
|
|
```
|
|
|
|
**Estimated Time:** 4 hours
|
|
|
|
#### Step 4.3: Apply Role Checks to Elevated Endpoints
|
|
|
|
**Endpoints requiring elevated privileges:**
|
|
|
|
1. **User Management:**
|
|
- `POST /api/v1/auth/users/` - Create user (admin/owner)
|
|
- `PUT /api/v1/auth/users/{id}/` - Update user (admin/owner)
|
|
- `DELETE /api/v1/auth/users/{id}/` - Delete user (admin/owner)
|
|
- `POST /api/v1/auth/users/invite/` - Invite user (admin/owner)
|
|
|
|
2. **Account Management:**
|
|
- `POST /api/v1/auth/accounts/` - Create account (admin/owner)
|
|
- `PUT /api/v1/auth/accounts/{id}/` - Update account (admin/owner)
|
|
- `DELETE /api/v1/auth/accounts/{id}/` - Delete account (admin/owner)
|
|
|
|
3. **Credit Management:**
|
|
- `POST /api/v1/billing/credits/transactions/` - Create transaction (admin/owner)
|
|
- Credit balance updates (admin/owner)
|
|
|
|
4. **Integration Settings:**
|
|
- `POST /api/v1/system/settings/integrations/{pk}/save/` - Save settings (admin/owner)
|
|
- `POST /api/v1/system/settings/integrations/{pk}/test/` - Test connection (admin/owner)
|
|
|
|
5. **System Settings:**
|
|
- `POST /api/v1/system/settings/system/` - Create system setting (admin/owner)
|
|
- `PUT /api/v1/system/settings/system/{id}/` - Update system setting (admin/owner)
|
|
|
|
**Example Application:**
|
|
|
|
```python
|
|
from igny8_core.api.viewsets import BaseTenantViewSet
|
|
from igny8_core.api.permissions import IsAdminOrOwner
|
|
|
|
class UsersViewSet(BaseTenantViewSet):
|
|
permission_classes = [
|
|
IsAuthenticatedAndActive,
|
|
HasTenantAccess,
|
|
IsAdminOrOwner, # Require admin or owner role
|
|
]
|
|
queryset = User.objects.all()
|
|
serializer_class = UserSerializer
|
|
```
|
|
|
|
**Estimated Time:** 6 hours
|
|
|
|
---
|
|
|
|
## Task 5: Secure Custom Actions
|
|
|
|
### Goal
|
|
Ensure all custom actions (`@action` methods) have proper permission checks and payload validation.
|
|
|
|
### Implementation Steps
|
|
|
|
#### Step 5.1: Audit All Custom Actions
|
|
|
|
**Action Items:**
|
|
1. List all `@action` decorators in all ViewSets
|
|
2. Document current permission checks (if any)
|
|
3. Identify actions that need explicit permissions
|
|
4. Identify actions that need payload validation
|
|
|
|
**Custom Actions to Audit:**
|
|
|
|
**Planner Module:**
|
|
- `POST /api/v1/planner/keywords/bulk_delete/`
|
|
- `POST /api/v1/planner/keywords/bulk_update_status/`
|
|
- `POST /api/v1/planner/keywords/bulk_add_from_seed/`
|
|
- `POST /api/v1/planner/keywords/auto_cluster/`
|
|
- `POST /api/v1/planner/clusters/auto_generate_ideas/`
|
|
- `POST /api/v1/planner/ideas/bulk_queue_to_writer/`
|
|
|
|
**Writer Module:**
|
|
- `POST /api/v1/writer/tasks/auto_generate_content/`
|
|
- `POST /api/v1/writer/content/generate_image_prompts/`
|
|
- `POST /api/v1/writer/images/generate_images/`
|
|
- `POST /api/v1/writer/images/bulk_update/`
|
|
|
|
**System Module:**
|
|
- `POST /api/v1/system/prompts/save/`
|
|
- `POST /api/v1/system/prompts/reset/`
|
|
- `POST /api/v1/system/settings/integrations/{pk}/test/`
|
|
- `POST /api/v1/system/settings/integrations/{pk}/generate/`
|
|
|
|
**Estimated Time:** 4 hours
|
|
|
|
#### Step 5.2: Secure Custom Actions
|
|
|
|
**Implementation Pattern:**
|
|
|
|
**Before:**
|
|
```python
|
|
class KeywordViewSet(SiteSectorModelViewSet):
|
|
@action(detail=False, methods=['post'])
|
|
def bulk_delete(self, request):
|
|
ids = request.data.get('ids', [])
|
|
# No permission check, no validation
|
|
deleted_count = Keyword.objects.filter(id__in=ids).delete()[0]
|
|
return Response({"deleted_count": deleted_count})
|
|
```
|
|
|
|
**After:**
|
|
```python
|
|
from igny8_core.api.permissions import IsAuthenticatedAndActive, HasTenantAccess
|
|
|
|
class KeywordViewSet(BaseTenantViewSet):
|
|
@action(
|
|
detail=False,
|
|
methods=['post'],
|
|
permission_classes=[IsAuthenticatedAndActive, HasTenantAccess]
|
|
)
|
|
def bulk_delete(self, request):
|
|
ids = request.data.get('ids', [])
|
|
|
|
# Validate payload
|
|
if not ids or not isinstance(ids, list):
|
|
return error_response(
|
|
error="Invalid payload: 'ids' must be a non-empty list",
|
|
status_code=status.HTTP_400_BAD_REQUEST
|
|
)
|
|
|
|
# Ensure all IDs belong to current user's account
|
|
queryset = self.get_queryset() # Already filtered by account
|
|
keywords = queryset.filter(id__in=ids)
|
|
|
|
# Validate all IDs exist and belong to account
|
|
if keywords.count() != len(ids):
|
|
return error_response(
|
|
error="Some keywords not found or don't belong to your account",
|
|
status_code=status.HTTP_403_FORBIDDEN
|
|
)
|
|
|
|
deleted_count = keywords.delete()[0]
|
|
return success_response(
|
|
data={"deleted_count": deleted_count},
|
|
message=f"Successfully deleted {deleted_count} keywords"
|
|
)
|
|
```
|
|
|
|
**Key Security Checks:**
|
|
1. Explicit `permission_classes` on `@action` decorator
|
|
2. Validate payload structure and types
|
|
3. Use `get_queryset()` to ensure account filtering
|
|
4. Verify all IDs belong to user's account before processing
|
|
5. Return appropriate error responses for validation failures
|
|
|
|
**Estimated Time:** 8 hours
|
|
|
|
---
|
|
|
|
## Task 6: Validate Token and Session Auth Coexistence
|
|
|
|
### Goal
|
|
Ensure both token-based and session-based authentication work correctly and are used appropriately.
|
|
|
|
### Implementation Steps
|
|
|
|
#### Step 6.1: Review Current Authentication Configuration
|
|
|
|
**File:** `backend/igny8_core/settings.py`
|
|
|
|
**Current Configuration:**
|
|
```python
|
|
REST_FRAMEWORK = {
|
|
'DEFAULT_AUTHENTICATION_CLASSES': [
|
|
'igny8_core.api.authentication.JWTAuthentication', # Primary
|
|
'igny8_core.api.authentication.CSRFExemptSessionAuthentication', # Fallback
|
|
'rest_framework.authentication.BasicAuthentication', # Fallback
|
|
],
|
|
'DEFAULT_PERMISSION_CLASSES': [
|
|
'rest_framework.permissions.AllowAny', # Will be changed to IsAuthenticatedAndActive
|
|
],
|
|
}
|
|
```
|
|
|
|
**Action Items:**
|
|
1. Verify authentication classes are in correct order (JWT first)
|
|
2. Ensure CSRFExemptSessionAuthentication is properly configured
|
|
3. Document which endpoints should use which auth method
|
|
4. Test authentication fallback behavior
|
|
|
|
**Estimated Time:** 2 hours
|
|
|
|
#### Step 6.2: Test Authentication Coexistence
|
|
|
|
**Test Cases:**
|
|
1. **JWT Token Authentication:**
|
|
- Valid JWT token in `Authorization: Bearer <token>` header
|
|
- Should authenticate successfully
|
|
- Should set `request.user` and `request.account`
|
|
|
|
2. **Session Authentication:**
|
|
- Valid session cookie (from Django admin or session login)
|
|
- Should authenticate successfully as fallback
|
|
- Should work for admin panel (`/admin/`)
|
|
|
|
3. **Basic Authentication:**
|
|
- Valid HTTP Basic Auth credentials
|
|
- Should authenticate as last fallback
|
|
- Should work for API testing tools
|
|
|
|
4. **No Authentication:**
|
|
- No token, no session, no basic auth
|
|
- Should return 401 Unauthorized for protected endpoints
|
|
- Should allow access for public endpoints (AllowAny)
|
|
|
|
5. **Invalid Token:**
|
|
- Expired JWT token
|
|
- Invalid JWT token format
|
|
- Should fall back to session auth, then basic auth
|
|
- Should return 401 if all fail
|
|
|
|
**Estimated Time:** 1 hour
|
|
|
|
#### Step 6.3: Document Authentication Usage
|
|
|
|
**File:** `docs/04-BACKEND-IMPLEMENTATION.md` (or create new auth doc)
|
|
|
|
**Add Section: "Authentication Methods"**
|
|
|
|
```markdown
|
|
## Authentication Methods
|
|
|
|
### JWT Token Authentication (Primary)
|
|
- **Use Case:** Frontend application API calls
|
|
- **Header:** `Authorization: Bearer <token>`
|
|
- **Token Type:** Access token (15-minute expiry)
|
|
- **Token Payload:** `user_id`, `account_id`, `type: 'access'`
|
|
- **Account Context:** Automatically sets `request.account` from token
|
|
|
|
### Session Authentication (Fallback)
|
|
- **Use Case:** Django admin panel (`/admin/`)
|
|
- **Method:** Session cookies (CSRF exempt for API)
|
|
- **Configuration:** `CSRFExemptSessionAuthentication`
|
|
- **Note:** Only used when JWT authentication fails
|
|
|
|
### Basic Authentication (Fallback)
|
|
- **Use Case:** API testing tools (Postman, curl)
|
|
- **Method:** HTTP Basic Auth
|
|
- **Note:** Only used when JWT and session authentication fail
|
|
|
|
### Authentication Order
|
|
1. JWT Token Authentication (tried first)
|
|
2. Session Authentication (fallback)
|
|
3. Basic Authentication (last fallback)
|
|
4. If all fail: 401 Unauthorized
|
|
```
|
|
|
|
**Estimated Time:** 1 hour
|
|
|
|
---
|
|
|
|
## Task 7: Frontend Sync + Fallback UX
|
|
|
|
### Goal
|
|
Ensure frontend properly handles authentication errors and role-based UI restrictions.
|
|
|
|
### Implementation Steps
|
|
|
|
#### Step 7.1: Update Frontend Error Handling
|
|
|
|
**File:** `frontend/src/services/api.ts`
|
|
|
|
**Current Error Handling:**
|
|
- Check for 401/403 responses
|
|
- Handle token refresh
|
|
- Route to login on authentication failure
|
|
|
|
**Updates Needed:**
|
|
1. **Enhanced 401 Handling:**
|
|
```typescript
|
|
if (response.status === 401) {
|
|
// Clear auth state
|
|
authStore.getState().logout();
|
|
// Show user-friendly message
|
|
showNotification('Session expired. Please log in again.', 'error');
|
|
// Redirect to login
|
|
router.push('/login');
|
|
}
|
|
```
|
|
|
|
2. **Enhanced 403 Handling:**
|
|
```typescript
|
|
if (response.status === 403) {
|
|
// Show permission error
|
|
showNotification('You do not have permission to perform this action.', 'error');
|
|
// Optionally redirect or show restricted UI
|
|
}
|
|
```
|
|
|
|
3. **Error Response Parsing:**
|
|
```typescript
|
|
// Parse unified error format
|
|
const errorData = await response.json();
|
|
if (errorData.success === false) {
|
|
const errorMessage = errorData.error || 'An error occurred';
|
|
const fieldErrors = errorData.errors || {};
|
|
// Handle error message and field errors
|
|
}
|
|
```
|
|
|
|
**Estimated Time:** 4 hours
|
|
|
|
#### Step 7.2: Update Frontend Role Storage
|
|
|
|
**File:** `frontend/src/stores/authStore.ts` (or similar)
|
|
|
|
**Updates Needed:**
|
|
1. **Store Role Information:**
|
|
```typescript
|
|
interface AuthState {
|
|
user: {
|
|
id: number;
|
|
email: string;
|
|
role: 'owner' | 'admin' | 'editor' | 'writer' | 'viewer';
|
|
account: {
|
|
id: number;
|
|
name: string;
|
|
};
|
|
};
|
|
// ... other fields
|
|
}
|
|
```
|
|
|
|
2. **Update on Login:**
|
|
```typescript
|
|
const login = async (email: string, password: string) => {
|
|
const response = await fetchAPI('/v1/auth/login/', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ email, password }),
|
|
});
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.user) {
|
|
set({
|
|
user: data.user,
|
|
token: data.tokens.access,
|
|
// Store role for UI guards
|
|
});
|
|
}
|
|
};
|
|
```
|
|
|
|
3. **Role Helper Functions:**
|
|
```typescript
|
|
export const hasRole = (requiredRole: string): boolean => {
|
|
const user = authStore.getState().user;
|
|
if (!user) return false;
|
|
|
|
const roleHierarchy = ['viewer', 'writer', 'editor', 'admin', 'owner'];
|
|
const userRoleIndex = roleHierarchy.indexOf(user.role);
|
|
const requiredRoleIndex = roleHierarchy.indexOf(requiredRole);
|
|
|
|
return userRoleIndex >= requiredRoleIndex;
|
|
};
|
|
|
|
export const isAdminOrOwner = (): boolean => {
|
|
const user = authStore.getState().user;
|
|
return user?.role === 'admin' || user?.role === 'owner';
|
|
};
|
|
```
|
|
|
|
**Estimated Time:** 2 hours
|
|
|
|
#### Step 7.3: Add UI Guards
|
|
|
|
**Implementation Pattern:**
|
|
|
|
**Component-Level Guards:**
|
|
```typescript
|
|
import { hasRole, isAdminOrOwner } from '@/utils/roles';
|
|
|
|
function UserManagementPage() {
|
|
const user = useAuthStore((state) => state.user);
|
|
|
|
// Check permission
|
|
if (!isAdminOrOwner()) {
|
|
return <AccessDenied message="Admin or Owner access required" />;
|
|
}
|
|
|
|
// Render component
|
|
return <UserManagement />;
|
|
}
|
|
```
|
|
|
|
**Button-Level Guards:**
|
|
```typescript
|
|
function SettingsPage() {
|
|
const canManageSettings = isAdminOrOwner();
|
|
|
|
return (
|
|
<div>
|
|
<button
|
|
disabled={!canManageSettings}
|
|
onClick={handleSaveSettings}
|
|
>
|
|
Save Settings
|
|
</button>
|
|
{!canManageSettings && (
|
|
<span className="text-muted">Admin access required</span>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
**Route-Level Guards:**
|
|
```typescript
|
|
// In router configuration
|
|
{
|
|
path: '/admin/users',
|
|
element: <ProtectedRoute requiredRole="admin"><UserManagement /></ProtectedRoute>,
|
|
}
|
|
```
|
|
|
|
**Files to Update:**
|
|
- Create `frontend/src/utils/roles.ts` - Role helper functions
|
|
- Create `frontend/src/components/ProtectedRoute.tsx` - Route guard component
|
|
- Create `frontend/src/components/AccessDenied.tsx` - Access denied component
|
|
- Update components that need role-based UI restrictions
|
|
|
|
**Estimated Time:** 4 hours
|
|
|
|
---
|
|
|
|
## Task 8: Changelog Entry
|
|
|
|
### Goal
|
|
Document the authentication and authorization standardization changes.
|
|
|
|
### Implementation Steps
|
|
|
|
#### Step 8.1: Update CHANGELOG.md
|
|
|
|
**File:** `CHANGELOG.md` (or similar)
|
|
|
|
**Add Entry:**
|
|
```markdown
|
|
## [Unreleased] - 2025-01-XX
|
|
|
|
### Changed
|
|
- **Authentication & Authorization**: Standardized authentication and permission enforcement across all API endpoints
|
|
- Created unified permission classes: `IsAuthenticatedAndActive`, `HasTenantAccess`, `IsAdminOrOwner`, `IsEditorOrAbove`, `IsViewerOrAbove`
|
|
- Created `BaseTenantViewSet` with automatic permission injection and tenant filtering
|
|
- Refactored all ViewSets across Auth, Planner, Writer, System, and Billing modules to use standardized permissions
|
|
- Secured all custom actions with explicit permission checks and payload validation
|
|
- Updated frontend error handling for 401/403 responses
|
|
- Added role-based UI guards and access controls
|
|
|
|
### Security
|
|
- **Breaking Change**: Most endpoints now require authentication (changed from `AllowAny` to `IsAuthenticatedAndActive`)
|
|
- Public endpoints (register, login, plans, industries, status) remain accessible without authentication
|
|
- All other endpoints now require valid JWT token or session authentication
|
|
- Tenant/account access is now enforced at the permission level
|
|
|
|
### Affected Areas
|
|
- API Layer (`igny8_core/api/permissions.py`, `igny8_core/api/viewsets.py`)
|
|
- Auth Module (`auth/api/`)
|
|
- Planner Module (`planner/api/`)
|
|
- Writer Module (`writer/api/`)
|
|
- System Module (`system/api/`)
|
|
- Billing Module (`billing/api/`)
|
|
- Frontend (`frontend/src/services/api.ts`, `frontend/src/stores/authStore.ts`)
|
|
|
|
### Migration Guide
|
|
1. **Frontend Updates Required:**
|
|
- Update API client to handle 401/403 errors gracefully
|
|
- Store user role information in auth store
|
|
- Add UI guards for role-based access
|
|
- Update error messages for authentication/permission failures
|
|
|
|
2. **API Testing:**
|
|
- All API tests must include authentication tokens
|
|
- Update test fixtures to use authenticated users
|
|
- Test role-based access for elevated endpoints
|
|
|
|
3. **Public Endpoints:**
|
|
- Registration, login, plans, industries, and status endpoints remain public
|
|
- All other endpoints require authentication
|
|
```
|
|
|
|
**Estimated Time:** 1 hour
|
|
|
|
---
|
|
|
|
## Testing Strategy
|
|
|
|
### Unit Tests
|
|
|
|
**File:** `backend/igny8_core/api/tests/test_permissions.py`
|
|
|
|
**Test Cases:**
|
|
- Permission classes with various user states
|
|
- Tenant access validation
|
|
- Role-based permission checks
|
|
- Object-level permissions
|
|
- System bot bypass logic
|
|
|
|
### Integration Tests
|
|
|
|
**Test Cases:**
|
|
- End-to-end API calls with authentication
|
|
- Authentication failure scenarios (401)
|
|
- Permission denial scenarios (403)
|
|
- Tenant isolation (user can't access other accounts)
|
|
- Role-based access control
|
|
- Custom action security
|
|
|
|
### Manual Testing
|
|
|
|
**Checklist:**
|
|
- [ ] All endpoints require authentication (except public ones)
|
|
- [ ] Tenant access is enforced correctly
|
|
- [ ] Role-based access works for elevated endpoints
|
|
- [ ] Custom actions have proper permission checks
|
|
- [ ] Payload validation works in custom actions
|
|
- [ ] Frontend handles 401/403 errors correctly
|
|
- [ ] UI guards hide/show features based on role
|
|
- [ ] Session auth works for admin panel
|
|
- [ ] JWT token auth works for API calls
|
|
|
|
---
|
|
|
|
## Rollout Plan
|
|
|
|
### Phase 1: Foundation (Week 1)
|
|
- ✅ Task 1: Create global permission classes
|
|
- ✅ Task 2: Create BaseTenantViewSet
|
|
- ✅ Unit tests for permission classes
|
|
|
|
### Phase 2: Module Refactoring (Week 2-3)
|
|
- ✅ Task 3.2: Refactor Auth module
|
|
- ✅ Task 3.3: Refactor Planner module
|
|
- ✅ Task 3.4: Refactor Writer module
|
|
- ✅ Task 3.5: Refactor System module
|
|
- ✅ Task 3.6: Refactor Billing module
|
|
|
|
### Phase 3: Role-Based Access (Week 4)
|
|
- ✅ Task 4: Implement role-based checks
|
|
- ✅ Task 5: Secure custom actions
|
|
- ✅ Integration testing
|
|
|
|
### Phase 4: Frontend & Validation (Week 5)
|
|
- ✅ Task 6: Validate auth coexistence
|
|
- ✅ Task 7: Frontend updates
|
|
- ✅ End-to-end testing
|
|
- ✅ Bug fixes and adjustments
|
|
|
|
### Phase 5: Documentation & Release (Week 6)
|
|
- ✅ Task 8: Changelog entry
|
|
- ✅ Documentation updates
|
|
- ✅ Release to staging
|
|
- ✅ Production deployment
|
|
|
|
---
|
|
|
|
## Success Criteria
|
|
|
|
### Definition of Done
|
|
|
|
1. ✅ All permission classes are defined and tested
|
|
2. ✅ BaseTenantViewSet is created and used by all ViewSets
|
|
3. ✅ All ViewSets have appropriate permission classes
|
|
4. ✅ All custom actions have explicit permission checks
|
|
5. ✅ Payload validation is implemented in custom actions
|
|
6. ✅ Frontend handles authentication errors correctly
|
|
7. ✅ Role-based UI guards are implemented
|
|
8. ✅ Documentation is updated
|
|
9. ✅ Changelog entry is created
|
|
10. ✅ All tests pass
|
|
|
|
### Metrics
|
|
|
|
- **Coverage:** 100% of endpoints have explicit permission classes
|
|
- **Security:** All endpoints enforce tenant access (except public ones)
|
|
- **Test Coverage:** >90% for permission classes
|
|
- **Breaking Changes:** Documented and migration guide provided
|
|
|
|
---
|
|
|
|
## Risk Assessment
|
|
|
|
### Risks
|
|
|
|
1. **Breaking Changes:** Frontend may break if endpoints now require auth
|
|
- **Mitigation:** Keep public endpoints public, provide migration guide, test frontend early
|
|
|
|
2. **Performance Impact:** Permission checks may add overhead
|
|
- **Mitigation:** Minimal overhead, cache permission checks if needed
|
|
|
|
3. **Incomplete Migration:** Some endpoints may be missed
|
|
- **Mitigation:** Comprehensive audit checklist, automated tests
|
|
|
|
4. **Role System Conflicts:** Existing role system may conflict
|
|
- **Mitigation:** Review existing role implementation, ensure compatibility
|
|
|
|
### Rollback Plan
|
|
|
|
If issues arise:
|
|
1. Revert permission class changes (keep classes but don't apply)
|
|
2. Keep BaseTenantViewSet (non-breaking)
|
|
3. Gradually roll back ViewSet changes if needed
|
|
4. Document issues for future fixes
|
|
|
|
---
|
|
|
|
## Appendix
|
|
|
|
### Permission Class Usage Guide
|
|
|
|
#### Public Endpoints (AllowAny)
|
|
```python
|
|
class RegisterView(APIView):
|
|
permission_classes = [AllowAny] # Public endpoint
|
|
```
|
|
|
|
#### Standard Authenticated Endpoints
|
|
```python
|
|
class MyViewSet(BaseTenantViewSet):
|
|
# Automatically uses IsAuthenticatedAndActive + HasTenantAccess
|
|
queryset = MyModel.objects.all()
|
|
```
|
|
|
|
#### Role-Based Endpoints
|
|
```python
|
|
class UsersViewSet(BaseTenantViewSet):
|
|
permission_classes = [
|
|
IsAuthenticatedAndActive,
|
|
HasTenantAccess,
|
|
IsAdminOrOwner, # Require admin or owner
|
|
]
|
|
```
|
|
|
|
#### Custom Action with Explicit Permissions
|
|
```python
|
|
class MyViewSet(BaseTenantViewSet):
|
|
@action(
|
|
detail=False,
|
|
methods=['post'],
|
|
permission_classes=[IsAuthenticatedAndActive, HasTenantAccess, IsAdminOrOwner]
|
|
)
|
|
def sensitive_action(self, request):
|
|
# Action implementation
|
|
pass
|
|
```
|
|
|
|
---
|
|
|
|
**Document Status:** Implementation Plan
|
|
**Last Updated:** 2025-01-XX
|
|
**Next Review:** After Phase 1 completion
|
|
|