Metadata-Version: 2.4
Name: govector-auth
Version: 0.1.7
Summary: Django authentication backends and views for Vector-generated apps
License-Expression: MIT
Requires-Python: >=3.11
Requires-Dist: cryptography>=41.0
Requires-Dist: django>=4.2
Requires-Dist: djangorestframework-simplejwt>=5.3
Requires-Dist: djangorestframework>=3.14
Requires-Dist: pyjwt>=2.8
Requires-Dist: requests>=2.31
Description-Content-Type: text/markdown

# govector-auth

Django authentication backends and views for Vector-generated apps. Handles JWT verification from Vector's auth proxy, session cookie management, and dev-mode auto-authentication.

## Install

```bash
pip install govector-auth
```

## Setup

### Authentication Backends

Add to your Django settings:

```python
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "govector_auth.JWTCookieAuthentication",
        "govector_auth.DevAutoAuthentication",
    ],
}
```

- `JWTCookieAuthentication` reads JWT from HttpOnly cookies with CSRF enforcement
- `DevAutoAuthentication` provides a fallback dev user when auth is not configured (when `VECTOR_AUTH_PROXY_URL` is not set)

### URL Configuration

Add the auth views to your `urls.py`:

```python
from govector_auth import AuthTokenView, TokenRefreshView, CurrentUserView, LogoutView

urlpatterns = [
    path("api/accounts/auth/token", AuthTokenView.as_view()),
    path("api/accounts/auth/refresh", TokenRefreshView.as_view()),
    path("api/accounts/me/", CurrentUserView.as_view()),
    path("api/accounts/logout/", LogoutView.as_view()),
]
```

### Middleware (CHIPS / Partitioned cookies)

Vector apps run inside a 3p iframe on `govector.ai`. Chrome (especially incognito) drops 3p cookies unless they carry the `Partitioned` (CHIPS) attribute. The auth views in this package already stamp `Partitioned` on the auth cookies they set/delete, but Django's `CsrfViewMiddleware` sets `csrftoken` independently — so add `PartitionedCsrfCookieMiddleware` to wrap it:

```python
MIDDLEWARE = [
    # ...
    # Must come BEFORE CsrfViewMiddleware. process_response runs bottom-up,
    # so being above it in the list means this runs AFTER it on the response
    # side and can stamp the csrftoken cookie it just set.
    "govector_auth.PartitionedCsrfCookieMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    # ...
]
```

### Required Settings

```python
# Cookie names
ACCESS_TOKEN_COOKIE = "access_token"
REFRESH_TOKEN_COOKIE = "refresh_token"

# Cookie config
ACCESS_TOKEN_COOKIE_MAX_AGE = 60 * 5  # 5 minutes
ACCESS_TOKEN_COOKIE_HTTPONLY = True
ACCESS_TOKEN_COOKIE_SECURE = True  # False for local dev
ACCESS_TOKEN_COOKIE_SAMESITE = "Lax"
ACCESS_TOKEN_COOKIE_PATH = "/"

REFRESH_TOKEN_COOKIE_MAX_AGE = 60 * 60 * 24 * 7  # 7 days
REFRESH_TOKEN_COOKIE_HTTPONLY = True
REFRESH_TOKEN_COOKIE_SECURE = True  # False for local dev
REFRESH_TOKEN_COOKIE_SAMESITE = "Lax"
REFRESH_TOKEN_COOKIE_PATH = "/"
```

### Required Environment Variables

```bash
VECTOR_AUTH_PROXY_URL=https://auth.govector.ai  # Vector's hosted auth proxy
DEV_USER_EMAIL=dev@localhost                     # Fallback email when auth is disabled
```

### User Model

Your User model should include these fields for full compatibility:

```python
class User(AbstractBaseUser):
    email = models.EmailField(unique=True)
    first_name = models.CharField(max_length=150, blank=True)
    last_name = models.CharField(max_length=150, blank=True)
    picture = models.URLField(max_length=500, blank=True, default="")
    vector_uga_user_id = models.CharField(max_length=255, blank=True, default="")
    is_active = models.BooleanField(default=True)

    @property
    def full_name(self) -> str:
        return f"{self.first_name} {self.last_name}".strip() or self.email
```

## Auth Flow

1. Frontend POSTs JWT from auth proxy to `AuthTokenView`
2. View fetches RS256 public key from `VECTOR_AUTH_PROXY_URL/.well-known/jwks.json`
3. Verifies JWT signature, extracts user claims
4. Creates or updates local User record
5. Issues SimpleJWT access + refresh tokens as HttpOnly cookies
6. Subsequent requests authenticated via `JWTCookieAuthentication`
7. `TokenRefreshView` rotates tokens when access token expires

## API

### `JWTCookieAuthentication`

DRF authentication backend. Reads JWT from the `ACCESS_TOKEN_COOKIE` cookie, falls back to `Authorization` header. Enforces CSRF on unsafe methods when using cookies.

### `DevAutoAuthentication`

DRF authentication backend. When `VECTOR_AUTH_PROXY_URL` is not set, auto-authenticates with a shared dev user. When auth is enabled, returns `None` (passes through to 401).

### Views

| View | Method | Path | Description |
|---|---|---|---|
| `AuthTokenView` | POST | `/api/accounts/auth/token` | Exchange auth proxy JWT for session |
| `TokenRefreshView` | POST | `/api/accounts/auth/refresh` | Rotate refresh token |
| `CurrentUserView` | GET | `/api/accounts/me/` | Get current user |
| `LogoutView` | POST | `/api/accounts/logout/` | Clear auth cookies |

### Middleware

| Middleware | Description |
|---|---|
| `PartitionedCsrfCookieMiddleware` | Stamps `Partitioned` (CHIPS) on Django's `csrftoken` cookie so Chrome accepts it in 3p iframe context. |
