Metadata-Version: 2.4
Name: web42-auth
Version: 0.1.0
Summary: Web42 Auth SDK — token introspection client and framework middleware helpers
License: MIT
Project-URL: Homepage, https://github.com/yarn-rp/web42-network
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.24
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.100; extra == "fastapi"
Requires-Dist: starlette>=0.27; extra == "fastapi"
Provides-Extra: flask
Requires-Dist: flask>=2.3; extra == "flask"
Provides-Extra: a2a
Requires-Dist: a2a-sdk>=0.3; extra == "a2a"
Requires-Dist: starlette>=0.27; extra == "a2a"
Requires-Dist: uvicorn>=0.20; extra == "a2a"
Provides-Extra: all
Requires-Dist: fastapi>=0.100; extra == "all"
Requires-Dist: starlette>=0.27; extra == "all"
Requires-Dist: flask>=2.3; extra == "all"
Requires-Dist: a2a-sdk>=0.3; extra == "all"
Requires-Dist: uvicorn>=0.20; extra == "all"

# web42-auth (Python)

Token introspection client and framework middleware helpers for the [Web42](https://web42.network) auth platform.

## Installation

```bash
# Core client only
pip install web42-auth

# With FastAPI middleware
pip install 'web42-auth[fastapi]'

# With Flask middleware
pip install 'web42-auth[flask]'

# With A2A server support (includes uvicorn)
pip install 'web42-auth[a2a]'

# Everything
pip install 'web42-auth[all]'
```

## Quick start

```python
from web42_auth import Web42Client

client = Web42Client(
    client_id="your-client-id",
    client_secret="your-client-secret",
)

token_info = client.introspect("Bearer eyJ...")
if token_info.active:
    print(token_info.sub)  # subject (user/agent ID)
```

### Async

```python
from web42_auth import AsyncWeb42Client

client = AsyncWeb42Client(
    client_id="your-client-id",
    client_secret="your-client-secret",
)

token_info = await client.introspect("eyJ...")
```

## Reference

### `Web42Client` / `AsyncWeb42Client`

| Parameter       | Type  | Description                             |
|-----------------|-------|-----------------------------------------|
| `client_id`     | `str` | OAuth2 client ID                        |
| `client_secret` | `str` | OAuth2 client secret                    |

**Methods:**

- `introspect(token: str) -> TokenInfo` — validate a Bearer token

### `TokenInfo`

| Field    | Type           | Description                        |
|----------|----------------|------------------------------------|
| `active` | `bool`         | Whether the token is valid         |
| `sub`    | `str \| None`  | Subject (user or agent identifier) |
| `exp`    | `int \| None`  | Expiry timestamp (Unix)            |
| `iat`    | `int \| None`  | Issued-at timestamp (Unix)         |
| `scope`  | `str \| None`  | Space-separated scopes             |

### `Web42AuthError`

Raised when the auth server returns an unexpected error response.

---

## FastAPI middleware

```python
import os
from fastapi import FastAPI
from web42_auth import AsyncWeb42Client
from web42_auth.middleware.fastapi import make_require_token

w42 = AsyncWeb42Client(
    client_id=os.environ["WEB42_CLIENT_ID"],
    client_secret=os.environ["WEB42_CLIENT_SECRET"],
)

app = FastAPI()
require_token = make_require_token(w42)

@app.get("/protected", dependencies=[Depends(require_token)])
async def protected():
    return {"status": "ok"}
```

Environment variables: `WEB42_CLIENT_ID`, `WEB42_CLIENT_SECRET`

---

## Flask middleware

```python
import os
from flask import Flask
from web42_auth import Web42Client
from web42_auth.middleware.flask import Web42FlaskMiddleware

w42 = Web42Client(
    client_id=os.environ["WEB42_CLIENT_ID"],
    client_secret=os.environ["WEB42_CLIENT_SECRET"],
)

app = Flask(__name__)
Web42FlaskMiddleware(app, client=w42)
```

---

## A2A server

Build an [A2A-protocol](https://google.github.io/A2A/) agent server with Web42 Bearer auth enforced on every request:

```python
import os
from web42_auth import AsyncWeb42Client
from web42_auth.a2a import create_a2a_server, AgentCardOptions

w42 = AsyncWeb42Client(
    client_id=os.environ["WEB42_CLIENT_ID"],
    client_secret=os.environ["WEB42_CLIENT_SECRET"],
)

server = create_a2a_server(
    web42=w42,
    card=AgentCardOptions(
        name="My Agent",
        description="Does cool things",
        base_url="http://localhost:8000",
        skills=[{"id": "cool", "name": "Cool Skill", "description": "..."}],
    ),
    executor=MyCoolExecutor(),
)

server.listen(port=8000)
```

`server.app` exposes the raw Starlette ASGI app if you need to mount it under an existing FastAPI application:

```python
fastapi_app.mount("/", server.app)
```

### Agent card helpers

```python
from web42_auth.a2a import build_agent_card, build_agent_card_security, AgentCardOptions

card = build_agent_card(AgentCardOptions(
    name="My Agent",
    description="...",
    base_url="https://my-agent.example.com",
))
# Returns a dict compatible with a2a.types.AgentCard

security = build_agent_card_security()
# Returns {"securitySchemes": {...}, "security": [...]}
```

| Symbol                  | Description                                              |
|-------------------------|----------------------------------------------------------|
| `create_a2a_server`     | Factory — returns `A2AServer`                            |
| `A2AServer`             | Wraps the ASGI app; has `.listen()` and `.app`           |
| `AgentCardOptions`      | Dataclass for agent card configuration                   |
| `build_agent_card`      | Build a full agent card dict                             |
| `build_agent_card_security` | Build the Web42 Bearer security scheme block         |
| `WEB42_SECURITY_SCHEME` | Constant `"Web42Bearer"` used in agent cards             |

## AP2 Payments

Build and parse AP2 payment mandates for agent commerce.

```python
from web42_auth import (
    build_cart_mandate,
    build_cart_mandate_data_part,
    build_cart_mandate_artifact,
    is_cart_mandate_part,
    is_payment_mandate_part,
    parse_cart_mandate,
    parse_payment_mandate,
    verify_payment,
    async_verify_payment,
)
```

### Types

| Type | Description |
|---|---|
| `AP2Amount` | Dataclass: `currency: str`, `value: float` |
| `DisplayItem` | Dataclass: `label: str`, `amount: AP2Amount` |
| `CartMandate` | Full cart mandate with contents + merchant signature |
| `PaymentMandate` | Payment proof with contents + user authorization JWT |
| `PaymentVerification` | Dataclass: `valid: bool`, `payment_mandate_id`, `payer_id`, `amount`, etc. |

### Building a CartMandate (merchant agents)

```python
from web42_auth import build_cart_mandate, build_cart_mandate_data_part, AP2Amount, DisplayItem

cart = build_cart_mandate(
    order_id="order_latte_456",
    items=[
        DisplayItem(label="Iced Latte", amount=AP2Amount(currency="USD", value=4.50)),
        DisplayItem(label="Tax", amount=AP2Amount(currency="USD", value=0.45)),
    ],
    total=DisplayItem(label="Total", amount=AP2Amount(currency="USD", value=4.95)),
    merchant_signature="sig_starbucks_456",
)

data_part = build_cart_mandate_data_part(cart)
```

### Parsing mandates (shopping agents)

```python
from web42_auth import is_payment_mandate_part, parse_payment_mandate

for part in message["parts"]:
    if is_payment_mandate_part(part):
        mandate = parse_payment_mandate(part)
        print(mandate.user_authorization)  # JWT token
```

### Verifying payment (merchant agents)

Sync:
```python
from web42_auth import Web42Client, verify_payment

client = Web42Client(
    client_id="...",
    client_secret="...",
)

result = verify_payment(client, payment_token, "order_latte_456")
if result.valid:
    print(f"Verified: ${result.amount.value} from {result.payer_id}")
```

Async:
```python
from web42_auth import AsyncWeb42Client, async_verify_payment

client = AsyncWeb42Client(...)
result = await async_verify_payment(client, payment_token, "order_latte_456")
```
