Metadata-Version: 2.4
Name: modal-auth
Version: 0.3.1
Summary: Authorization middleware for Spandrel Modal services
Requires-Python: >=3.11
Requires-Dist: fastapi>=0.116.1
Requires-Dist: pyjwt[crypto]==2.10.1
Description-Content-Type: text/markdown

# modal-auth

[![PyPI](https://img.shields.io/pypi/v/modal-auth)](https://pypi.org/project/modal-auth/)
[![Python](https://img.shields.io/pypi/pyversions/modal-auth)](https://pypi.org/project/modal-auth/)

Server-side authorization middleware for Spandrel Modal services.
The package validates short-lived Spandrel API JWTs locally and requires both
the OAuth scope and server-derived permission assigned to a skill.
It accepts only the exact audience set emitted for the native OIDC flow: the
`https://api.ndrel.spa` resource and Better Auth's issuer-local UserInfo
endpoint. Any additional audience fails closed.

```bash
pip install modal-auth
```

```python
from pydantic import BaseModel
from fastapi import Request, Response
from spandrel_modal_auth import Skill, authenticate

class PredictionRequest(BaseModel):
    sequence: str

def predict(
    payload: PredictionRequest,
    request: Request,
    response: Response,
) -> dict[str, str]:
    caller = authenticate(request, response, skill=Skill.USE_RIDGEY)
    return {"email": caller.email}
```

Use `scopes={"skills:read"}, permissions={"skills:read"}` for the skill
registry, or `any_skill={...}` only for an endpoint intentionally shared by
multiple skills. `/healthz` is the only public path by default.

The package is publicly installable because verification does not depend on
source secrecy. It contains public verification configuration and no signing
key, AWS credential, provider credential, refresh token, skill content, or
entitlement database access. Spandrel's auth server, skills, entitlement data,
and compute services remain private.

## Adding a skill

`spandrel_ai/auth/skill-access.json` is the product source of truth. After a
new skill and its authorization entry are ready, update this package manually:

```bash
uv run python scripts/sync_skill_access.py \
  --source ../spandrel_ai/auth/skill-access.json
```

The command imports the canonical contract and regenerates the public `Skill`
enum. It rejects invalid mappings and Python-name collisions. Review both
generated changes, run the complete verification commands in `AGENTS.md`, bump
the package version, and merge them before manually dispatching **Publish to
PyPI**. Publication is deliberately never triggered by a skill commit.

Modal caches image layers, so an unpinned verifier must deliberately refresh
its small package layer while leaving heavier application dependencies cached:

```python
image = base_image.pip_install(
    "modal-auth",
    index_url="https://pypi.org/simple",
    force_build=True,
)
```

This resolves the newest published verifier on each image build without waiting
for Modal's package mirror. Existing deployed images remain unchanged until
rebuilt.
