pargo-auth 0.1.6
Shared authentication middleware for PARGO backend services
Requires Python: >=3.10

# pargo-auth

Authentication and user management for PARGO. This repo serves two purposes:

1. **PyPI Package** (`pargo-auth`) - JWT verification middleware for FastAPI
2. **User Service** - API for user profile and settings management

## Architecture

```
pargo-auth/
├── src/pargo_auth/          # Library (published to PyPI)
│   ├── middleware.py        # Supabase JWT verification
│   ├── models.py            # AuthenticatedUser dataclass
│   └── exceptions.py        # Auth errors
├── app/                     # Service (deployed to server)
│   ├── main.py              # FastAPI application
│   ├── routes/users.py      # /v1/me endpoints
│   └── services/            # Database operations
├── docker/Dockerfile        # Service container
└── pyproject.toml           # Package configuration
```

---

## Part 1: PyPI Package

The `pargo-auth` package provides JWT verification for any PARGO backend.

### Installation

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

### Usage

```python
from fastapi import Depends
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, "email": user.email}
```

### Configuration

Set environment variables:

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

### AuthenticatedUser

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

### Optional Auth

For endpoints that work with or without authentication:

```python
from pargo_auth import SupabaseAuth

auth = SupabaseAuth()

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

---

## Part 2: User Service

The service manages user data in Hetzner Postgres (`auth` schema).

### Endpoints

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/v1/me` | Required | Get profile + settings (creates user on first call) |
| PATCH | `/v1/me` | Required | Update profile |
| PATCH | `/v1/me/settings` | Required | Update settings |
| DELETE | `/v1/me` | Required | Soft delete account |
| GET | `/health` | None | Health check |

### Database Schema

Uses the `auth` schema in Postgres:

- `auth.users` - Core user identity (synced from Supabase JWT)
- `auth.user_identities` - Login methods (apple, google, email)
- `auth.user_settings` - App preferences
- `auth.audit_events` - Security/login event log

### Running Locally

```bash
# Install dependencies
pip install -r requirements-service.txt
pip install -e .

# Set environment
export SUPABASE_URL=https://your-project.supabase.co
export DATABASE_URL=postgresql://postgres:password@localhost:5432/pargo_test
export ENV=local

# Run
uvicorn app.main:app --reload --port 8040
```

### Docker

```bash
docker build -f docker/Dockerfile -t pargo-auth .
docker run -p 8040:8040 \
  -e SUPABASE_URL=https://your-project.supabase.co \
  -e DATABASE_URL=postgresql://... \
  pargo-auth
```

### Response Examples

**GET /v1/me**

```json
{
  "user": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "supabase_uid": "846eddd7-f5ae-4d72-8082-accde7da68f2",
    "email": "user@example.com",
    "email_verified": true,
    "display_name": "John Doe",
    "preferred_lang": "da",
    "created_at": "2026-02-02T10:00:00Z",
    "last_login_at": "2026-02-02T12:00:00Z"
  },
  "settings": {
    "push_enabled": true,
    "email_enabled": true,
    "map_style": "standard",
    "settings_json": {}
  }
}
```

---

## Development

### Package Development

```bash
# Install in editable mode
pip install -e ".[dev]"

# Run tests
pytest
```

### Publishing (automatic)

Push to `main` triggers GitHub Actions:
1. Bumps version
2. Publishes to PyPI

---

## Design Decisions

| Decision | Rationale |
|----------|-----------|
| `supabase_uid` is canonical ID | JWT `sub` is stable even if email changes |
| User created on first `/me` call | No separate signup flow needed |
| Soft delete | GDPR compliance, account recovery possible |
| Separate settings table | Clean separation, flexible JSONB for future |