Metadata-Version: 2.4
Name: noesis-auth
Version: 0.2.0
Summary: Python Auth SDK for AI tool integration with the Noesis AIToolCenter platform
Author: Noesis AI Technologies
License-Expression: MIT
Project-URL: Homepage, https://github.com/Noesis-AI-Technologies/AIToolCenter
Project-URL: Documentation, https://github.com/Noesis-AI-Technologies/AIToolCenter/blob/main/docs/auth-sdk-guide.md
Project-URL: Repository, https://github.com/Noesis-AI-Technologies/AIToolCenter
Project-URL: Issues, https://github.com/Noesis-AI-Technologies/AIToolCenter/issues
Keywords: auth,oauth2,jwt,sdk,ai-tools
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.24.0
Requires-Dist: python-jose[cryptography]>=3.3.0
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.100.0; extra == "fastapi"

# noesis-auth (Python)

Python Auth SDK for AI tool integration with the [Noesis AIToolCenter](https://github.com/Noesis-AI-Technologies/AIToolCenter) platform.

## Installation

```bash
pip install noesis-auth

# With FastAPI middleware support:
pip install noesis-auth[fastapi]
```

## Quick Start

### JWT Validation (Tool-side)

```python
from noesis_auth import JWTValidator

validator = JWTValidator(
    jwks_url="https://your-platform.com/.well-known/jwks.json"
)

payload = await validator.validate(token)
print(payload["sub"])  # user ID
```

### FastAPI Middleware

```python
from fastapi import Depends, FastAPI
from noesis_auth import AuthMiddleware, TokenPayload

app = FastAPI()
auth = AuthMiddleware(jwks_url="https://your-platform.com/.well-known/jwks.json")

@app.get("/api/generate")
async def generate(payload: TokenPayload = Depends(auth.require_tool("your-tool-id"))):
    user_id = payload.sub
    # ... your tool logic
```

> `require_tool(tool_id)` and `has_tool_access(tool_id)` accept both `str` and `int` (auto-coerced to string).

### OAuth2 Client (PKCE)

```python
from noesis_auth import AuthClient

client = AuthClient(base_url="https://your-platform.com")

# Generate PKCE pair
pkce = AuthClient.generate_pkce()

# Build authorization URL
url = client.build_authorize_url(
    client_id="your-client-id",
    redirect_uri="http://localhost:3000/callback",
    code_challenge=pkce.code_challenge,
)

# Exchange code for tokens
tokens = await client.exchange_code(
    code=auth_code,
    redirect_uri="http://localhost:3000/callback",
    client_id="your-client-id",
    code_verifier=pkce.code_verifier,
)
```

### Activation Code Redemption

```python
result = await client.redeem_code(user_token="...", code="AXKF-M3PQ-7RBN-W2YT")
print(result.tool_id)         # "uuid-xxx"
print(result.tool_name)       # "AI Translator"
print(result.duration_days)   # 30
print(result.expires_at)      # "2026-06-23T10:00:00+00:00" (ISO datetime)
print(result.token_refresh_required)  # True
print(result.new_access_token)        # new JWT if token_refresh_required
```

### Token Introspection

```python
result = await client.introspect(token, client_id="...", client_secret="...")
if result.active:
    print(result.sub, result.entitlements)
```

### Token Refresh

```python
new_tokens = await client.refresh_token(
    refresh_token="...",
    client_id="your-client-id",
    client_secret="your-secret",
)
print(new_tokens.access_token)
```

## Features

- **JWT Validation** — RS256 (JWKS) and HS256 (shared secret) with auto-detection from token header
- **FastAPI Middleware** — Drop-in authentication and tool access verification
- **OAuth2 Client** — Authorization URL builder, code exchange, token refresh
- **PKCE Support** — S256 code challenge generation for public clients
- **Token Introspection** — Remote token validation endpoint
- **Activation Codes** — Redeem activation codes with full response (tool_name, duration, new_access_token)
- **JWKS Caching** — 6-hour configurable cache with stale-while-revalidate and retry on failure
- **Robust Error Handling** — Timeout, network errors, and malformed responses wrapped in `AuthError`

## Error Handling

All HTTP methods raise `AuthError` with a specific error code:

```python
from noesis_auth import AuthClient, AuthError

try:
    tokens = await client.exchange_code(...)
except AuthError as e:
    print(e.code)    # "TOKEN_INVALID", "NETWORK_ERROR", or "INVALID_RESPONSE"
    print(e.status)  # HTTP status code (0 for network errors)
    print(str(e))    # Human-readable message
```

| Code | Meaning |
|------|---------|
| `TOKEN_INVALID` | Server rejected the request (4xx/5xx) |
| `NETWORK_ERROR` | Timeout or connection failure |
| `INVALID_RESPONSE` | Server returned unparseable response |

## TokenPayload Fields

```python
@dataclass
class TokenPayload:
    sub: str                           # User ID
    exp: int                           # Expiration (unix timestamp)
    iat: int                           # Issued at (unix timestamp)
    jti: str                           # Unique token ID
    entitlements: list[Entitlement]    # Tool access list
    role: str | None                   # "integrator", "distributor", or None
    is_admin: bool                     # True for admin users
```

`has_tool_access(tool_id)` accepts both `str` and `int` (auto-coerced to string).

## Security Notes

- **No silent algorithm fallback**: If JWKS returns empty keys, validation fails explicitly instead of silently falling back to HS256.
- **HS256 only when token declares it**: The SDK uses HS256 only when the token's JWT header has `alg: HS256` and `hs256_secret` is configured.
- **Timeout protection**: All HTTP requests have a configurable timeout (default 10s). Hanging connections are terminated.

## Requirements

- Python >= 3.11
- `httpx` >= 0.24
- `python-jose[cryptography]` >= 3.3
- `fastapi` >= 0.100 (optional, for middleware)

## License

MIT
