Metadata-Version: 2.4
Name: modal-auth
Version: 0.4.0
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. It validates
short-lived EdDSA API JWTs locally and exposes typed capabilities, teams, and
safe caller identity. Verification accepts only the Spandrel API and issuer
UserInfo audiences and contains no secret material.

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

```python
from fastapi import Request, Response
from spandrel_modal_auth import Capability, authenticate

def predict(request: Request, response: Response):
    caller = authenticate(
        request,
        response,
        capability=Capability.USE_RIDGEY,
    )
    caller_email = caller.email
```

For a flag that selects a more restricted model, require both its capability
and team after the base request is authenticated:

```python
from spandrel_modal_auth import Capability, Team

if payload.model == "fibros":
    caller.require(capability=Capability.FIBROS, team=Team.INTEGRATED)
```

Use `protect(api)` for a platform service such as the skill registry, or
`protect(api, capability=Capability.USE_RIDGEY)` when every private route has
one fixed requirement. `/healthz` is the only public path by default.

The package is publicly installable because authorization does not depend on
source secrecy. Spandrel's signing keys, refresh tokens, skills, grants, and
compute services remain private.

## Updating the contract

`spandrel_ai/auth/capability-access.json` is the product source of truth. It
defines typed capabilities, optional installable-skill mappings, and teams.
After reviewing a contract change, import it manually:

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

Review the contract and generated module, run the complete verification gate
in `AGENTS.md`, bump the package version, and merge before manually dispatching
**Publish to PyPI**. Contract synchronization and publication are never
automatic.

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

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

Existing deployments remain unchanged until their image is rebuilt.
