Metadata-Version: 2.4
Name: pargo-auth
Version: 0.1.5
Summary: Shared authentication middleware for PARGO backend services
Project-URL: Homepage, https://github.com/pargoorg/pargo-auth
Project-URL: Repository, https://github.com/pargoorg/pargo-auth
Author-email: PARGO <dev@pargo.dk>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: FastAPI
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
Requires-Python: >=3.10
Requires-Dist: cryptography>=41.0.0
Requires-Dist: fastapi>=0.100.0
Requires-Dist: httpx>=0.24.0
Requires-Dist: pyjwt>=2.8.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Description-Content-Type: text/markdown

# pargo-auth

Shared authentication middleware for PARGO backend services. Verifies Supabase JWTs and provides FastAPI dependencies.

## Installation

```bash
pip install pargo-auth
```

## Quick Start

```python
from fastapi import FastAPI, Depends
from pargo_auth import get_current_user, AuthenticatedUser

app = FastAPI()

@app.get("/me")
async def get_me(user: AuthenticatedUser = Depends(get_current_user)):
    return {
        "id": user.sub,
        "email": user.email,
    }
```

## Configuration

Set these environment variables:

```bash
SUPABASE_URL=https://your-project.supabase.co
ENV=local  # Skip auth verification in local dev
```

## Usage Patterns

### Basic: Protect individual endpoints

```python
from pargo_auth import get_current_user, AuthenticatedUser

@app.get("/protected")
async def protected(user: AuthenticatedUser = Depends(get_current_user)):
    return {"user_id": user.sub}
```

### Protect entire router

```python
from fastapi import APIRouter, Depends
from pargo_auth import require_auth

router = APIRouter(dependencies=[require_auth()])

@router.get("/data")
async def get_data():  # Auth already required by router
    return {"data": "secret"}
```

### Optional auth (different behavior for logged-in vs anonymous)

```python
from pargo_auth import SupabaseAuth, AuthenticatedUser

auth = SupabaseAuth()

@app.get("/content")
async def get_content(user: AuthenticatedUser | None = Depends(auth.get_user_optional)):
    if user:
        return {"content": "personalized", "user": user.sub}
    return {"content": "generic"}
```

### Custom instance (non-default config)

```python
from pargo_auth import SupabaseAuth

auth = SupabaseAuth(
    supabase_url="https://custom.supabase.co",
    skip_verification_in_dev=False,  # Always verify, even locally
)

@app.get("/strict")
async def strict_endpoint(user = Depends(auth.get_user)):
    return {"user": user.sub}
```

## AuthenticatedUser

The `AuthenticatedUser` object contains:

| Field | Type | Description |
|-------|------|-------------|
| `sub` | `str` | Supabase user ID (stable, use as canonical identity) |
| `email` | `str \| None` | User's email (if available) |
| `email_verified` | `bool` | Whether email is verified |
| `raw_claims` | `dict \| None` | Full JWT payload for custom claims |

## Legacy Support

For backwards compatibility, the middleware also checks for `x-user-id` header if no Bearer token is present. This allows gradual migration from the old auth system.

## Development

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

# Run tests
pytest
```

## License

MIT
