Metadata-Version: 2.4
Name: uvb-django
Version: 0.2.0
Summary: Django middleware for Universal Verification Broker (UVB)
Project-URL: Homepage, https://gitlab.com/sparkz-community/security/uvb
Project-URL: Documentation, https://gitlab.com/sparkz-community/security/uvb/-/blob/main/docs/README.md
Project-URL: Repository, https://gitlab.com/sparkz-community/security/uvb
Project-URL: Issues, https://gitlab.com/sparkz-community/security/uvb/-/issues
Author: UVB Team
License: MIT
Keywords: authentication,django,mfa,middleware,security,uvb
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: Django
Classifier: Framework :: Django :: 3.2
Classifier: Framework :: Django :: 4.0
Classifier: Framework :: Django :: 4.1
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.0
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: django>=3.2
Requires-Dist: uvb-client>=0.1.0
Provides-Extra: dev
Requires-Dist: black>=23.0.0; extra == 'dev'
Requires-Dist: mypy>=1.0.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
Requires-Dist: pytest-django>=4.5.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Description-Content-Type: text/markdown

# uvb-django

Django middleware for Universal Verification Broker (UVB) authentication.

## Installation

```bash
pip install uvb-django
```

## Quick Start

### 1. Add Middleware

Add `UVBAuthenticationMiddleware` to your `MIDDLEWARE` in `settings.py`:

```python
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    # Add UVB middleware
    'uvb_django.middleware.UVBAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
```

### 2. Configure Settings

Add UVB configuration to your `settings.py`:

```python
# Required
UVB_TENANT_ID = 'my-tenant'

# Optional
UVB_URL = 'http://localhost:8080'  # Default: http://localhost:8080
UVB_API_KEY = 'your-api-key'  # For server-to-server auth
UVB_COOKIE_NAME = 'uvb_session'  # Default: uvb_session
UVB_EXCLUDE_PATHS = ['/login', '/health', '/admin']  # Paths to skip auth
```

### 3. Use in Views

```python
from django.http import HttpResponse, JsonResponse
from uvb_django import get_session, require_factors, uvb_required

# Access session in any view
def profile_view(request):
    session = get_session(request)
    if session:
        return JsonResponse({
            'user_id': session.user_id,
            'factors': session.factors_verified
        })
    return JsonResponse({'error': 'Not authenticated'}, status=401)

# Require authentication with decorator
@uvb_required
def protected_view(request):
    user_id = request.uvb_session.user_id
    return HttpResponse(f"Hello {user_id}")

# Require specific MFA factors
@require_factors('totp', 'webauthn')
def admin_view(request):
    return HttpResponse("Admin access granted")
```

## API Reference

### Middleware

#### `UVBAuthenticationMiddleware`

Django middleware that validates UVB sessions for all requests (except excluded paths).

**Configuration (settings.py):**

- `UVB_TENANT_ID` (required): Your UVB tenant ID
- `UVB_URL` (optional): UVB server URL, defaults to `http://localhost:8080`
- `UVB_API_KEY` (optional): API key for server-to-server authentication
- `UVB_COOKIE_NAME` (optional): Cookie name for session token, defaults to `uvb_session`
- `UVB_EXCLUDE_PATHS` (optional): List of path prefixes to exclude from authentication

**Request Extension:**

After successful authentication, `request.uvb_session` contains a `UVBSession` object:

```python
request.uvb_session.user_id         # User identifier
request.uvb_session.tenant_id       # Tenant identifier
request.uvb_session.session_id      # Session identifier
request.uvb_session.factors_verified  # List of verified factors
request.uvb_session.expires_at      # Session expiration datetime
request.uvb_session.status          # Session status
```

### Decorators

#### `@uvb_required`

Decorator to require UVB authentication for a view.

```python
from uvb_django import uvb_required

@uvb_required
def my_view(request):
    # Access authenticated user
    user_id = request.uvb_session.user_id
    return HttpResponse(f"Hello {user_id}")
```

#### `@require_factors(*factors)`

Decorator to require specific MFA factors for a view.

```python
from uvb_django import require_factors

@require_factors('totp', 'webauthn')
def admin_view(request):
    return HttpResponse("Admin action completed")
```

### Helper Functions

#### `get_session(request)`

Get the UVB session from a request.

```python
from uvb_django import get_session

def my_view(request):
    session = get_session(request)
    if session:
        return JsonResponse({'user_id': session.user_id})
    return JsonResponse({'error': 'Not authenticated'}, status=401)
```

## Examples

### Class-Based Views

```python
from django.views import View
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from uvb_django import uvb_required, require_factors, get_session

class ProfileView(View):
    @method_decorator(uvb_required)
    def get(self, request):
        session = get_session(request)
        return JsonResponse({
            'user_id': session.user_id,
            'factors': session.factors_verified
        })

class AdminView(View):
    @method_decorator(require_factors('totp', 'webauthn'))
    def delete(self, request, user_id):
        return JsonResponse({'message': f'User {user_id} deleted'})
```

### Django REST Framework

```python
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from uvb_django import get_session

class UserProfileAPI(APIView):
    def get(self, request):
        session = get_session(request)
        if not session:
            return Response(
                {'error': 'Not authenticated'},
                status=status.HTTP_401_UNAUTHORIZED
            )

        return Response({
            'user_id': session.user_id,
            'tenant_id': session.tenant_id,
            'factors_verified': session.factors_verified
        })
```

### Conditional MFA Requirements

```python
from django.http import JsonResponse
from uvb_django import get_session, require_factors

def transfer_view(request):
    session = get_session(request)
    if not session:
        return JsonResponse({'error': 'Not authenticated'}, status=401)

    amount = request.POST.get('amount', 0)

    # Require additional auth for large transfers
    if int(amount) > 10000:
        if not all(f in session.factors_verified for f in ['totp', 'webauthn']):
            return JsonResponse({
                'error': 'Additional authentication required',
                'required': ['totp', 'webauthn'],
                'verified': session.factors_verified
            }, status=403)

    return JsonResponse({'message': 'Transfer initiated'})
```

### Custom Error Handling

```python
# middleware.py
from uvb_django.middleware import UVBAuthenticationMiddleware
from django.http import JsonResponse

class CustomUVBMiddleware(UVBAuthenticationMiddleware):
    def __call__(self, request):
        # Custom logic before authentication
        if request.path.startswith('/public/'):
            return self.get_response(request)

        # Call parent middleware
        try:
            return super().__call__(request)
        except Exception as e:
            # Custom error handling
            return JsonResponse({
                'error': 'Authentication failed',
                'details': str(e)
            }, status=401)
```

### URL Patterns

```python
# urls.py
from django.urls import path
from . import views

urlpatterns = [
    # Public endpoints
    path('health/', views.health_check),
    path('login/', views.login),

    # Protected endpoints (middleware applies)
    path('api/profile/', views.profile_view),
    path('api/data/', views.data_view),

    # Admin endpoints with strict MFA
    path('admin/users/', views.admin_users_view),
    path('admin/delete/<int:user_id>/', views.admin_delete_view),
]
```

### Multiple Factor Checks

```python
from uvb_django import get_session

def sensitive_operation(request):
    session = get_session(request)
    if not session:
        return JsonResponse({'error': 'Not authenticated'}, status=401)

    # Check for at least one of multiple factors
    has_strong_auth = any(
        factor in session.factors_verified
        for factor in ['webauthn', 'totp']
    )

    if not has_strong_auth:
        return JsonResponse({
            'error': 'Strong authentication required',
            'message': 'Please verify with WebAuthn or TOTP'
        }, status=403)

    return JsonResponse({'message': 'Operation completed'})
```

## Development

```bash
# Install development dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Type checking
mypy uvb_django
```

## License

MIT
