Metadata-Version: 2.4
Name: h4ckath0n
Version: 0.1.2.dev20260210224738
Summary: Ship hackathon products fast with secure-by-default auth, RBAC, PostgreSQL readiness, and built-in LLM tooling.
Author-email: multiset <h4ckath0n@oss.joefang.org>
License: MIT
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: alembic>=1.14
Requires-Dist: cryptography>=46.0.5
Requires-Dist: fastapi>=0.115.0
Requires-Dist: httpx>=0.28
Requires-Dist: langchain-core>=0.3
Requires-Dist: langchain-openai>=0.3
Requires-Dist: langchain>=0.3
Requires-Dist: langgraph>=0.2
Requires-Dist: langsmith>=0.2
Requires-Dist: openai>=1.60
Requires-Dist: psycopg[binary]>=3.2
Requires-Dist: pydantic-settings>=2.7
Requires-Dist: pydantic[email]>=2.10
Requires-Dist: pyjwt[crypto]>=2.9
Requires-Dist: sqlalchemy>=2.0
Requires-Dist: sse-starlette>=2.0
Requires-Dist: uvicorn[standard]>=0.34.0
Requires-Dist: webauthn>=2.7
Provides-Extra: password
Requires-Dist: argon2-cffi>=23.1; extra == 'password'
Provides-Extra: redis
Requires-Dist: redis>=5.0; extra == 'redis'
Description-Content-Type: text/markdown

# h4ckath0n

Ship hackathon products fast, with secure-by-default auth, RBAC, Postgres readiness, and built-in LLM tooling.

**h4ckath0n** is an opinionated Python library that makes it hard to accidentally ship insecure glue code during a hackathon.

## What you get by default

- **API**: FastAPI app bootstrap with OpenAPI docs
- **Auth**: passkey (WebAuthn) registration and login – no passwords required
- **AuthZ**: built-in RBAC with `user` and `admin` roles, plus scoped permissions – all enforced server-side from the database
- **Database**: SQLAlchemy 2.x + Alembic, works with SQLite (zero-config dev) and Postgres (recommended for production)
- **LLM**: built-in LLM client wrapper (OpenAI SDK) with safe defaults and redaction hooks
- **Observability**: opt-in LangSmith / OpenTelemetry tracing with trace ID propagation
- **Config**: environment-driven settings via pydantic-settings

Password auth and Redis-based queues/caching are available as optional extras.

## Installation

### Recommended (uv)

```bash
uv add h4ckath0n
```

Optional extras:

```bash
uv add "h4ckath0n[password]"  # Argon2-based password auth (off by default)
uv add "h4ckath0n[redis]"     # Redis support
```

### pip

```bash
pip install h4ckath0n
```

## Scaffold a full-stack project

```bash
npx h4ckath0n my-app
```

## Quickstart

```python
from h4ckath0n import create_app

app = create_app()
```

Run:

```bash
uv run uvicorn your_module:app --reload
```

Open docs at `/docs` (Swagger UI). Passkey auth routes are mounted automatically.

## Auth: passkeys by default

h4ckath0n uses **passkeys (WebAuthn)** as the default authentication method. No passwords, no email required.

### How it works

1. **Register**: `POST /auth/passkey/register/start` → browser creates a passkey → `POST /auth/passkey/register/finish` → account created, device key bound.
2. **Login**: `POST /auth/passkey/login/start` → browser signs with passkey → `POST /auth/passkey/login/finish` → user identified, device key bound. Username-less by default.
3. **Add passkey**: authenticated users can add more passkeys via `POST /auth/passkey/add/start` + `POST /auth/passkey/add/finish`.
4. **Revoke passkey**: `POST /auth/passkeys/{key_id}/revoke` – but **cannot revoke the last active passkey** (returns `LAST_PASSKEY` error).

### Request authentication

All API calls are authenticated via **device-signed ES256 JWTs**:

- Each browser generates a non-extractable P-256 keypair stored in IndexedDB
- The public key is registered with the backend during login/registration
- The client mints short-lived JWTs (15 min) signed with the device private key
- JWTs are held in memory only – never `localStorage`, `sessionStorage`, or cookies
- The JWT contains **no privilege claims** (no role, no scopes) – authorization is computed server-side from the database

### ID scheme

- User IDs: 32-char base32 string starting with `u` (e.g., `u3mfgh7k2n4p5q6r7s8t9v0w1x2y3z4a`)
- Internal key IDs: 32-char base32 string starting with `k`
- The browser's WebAuthn `credentialId` is stored separately and used for signature verification.

## Secure-by-default endpoint protection

Protect an endpoint (requires a logged-in user):

```python
from h4ckath0n import create_app
from h4ckath0n.auth import require_user

app = create_app()

@app.get("/me")
def me(user=require_user()):
    return {"id": user.id, "role": user.role}
```

Admin-only endpoint:

```python
from h4ckath0n.auth import require_admin

@app.get("/admin/dashboard")
def admin_dashboard(user=require_admin()):
    return {"ok": True}
```

Scoped privileges (checked from user's DB record):

```python
from h4ckath0n.auth import require_scopes

@app.post("/billing/refund")
def refund(user=require_scopes("billing:refund")):
    return {"status": "queued"}
```

## Auth routes

h4ckath0n mounts these routes by default:

### Passkey (default)

- `POST /auth/passkey/register/start` – begin passkey registration (creates account)
- `POST /auth/passkey/register/finish` – complete registration (binds device key, returns user_id + device_id)
- `POST /auth/passkey/login/start` – begin passkey login (username-less)
- `POST /auth/passkey/login/finish` – complete login (binds device key, returns user_id + device_id)
- `POST /auth/passkey/add/start` – begin adding a passkey (authenticated)
- `POST /auth/passkey/add/finish` – complete adding a passkey (authenticated)
- `GET /auth/passkeys` – list current user's passkeys (authenticated)
- `POST /auth/passkeys/{key_id}/revoke` – revoke a passkey (authenticated, blocked if last)

### Password auth (optional extra)

Only available when `h4ckath0n[password]` is installed AND `H4CKATH0N_PASSWORD_AUTH_ENABLED=true`:

- `POST /auth/register` – create account with email + password, bind device key
- `POST /auth/login` – authenticate with email + password, bind device key
- `POST /auth/password-reset/request` – request password reset
- `POST /auth/password-reset/confirm` – confirm password reset, bind device key

Password auth is an identity bootstrap method only. It proves who the user is so a device key can be bound. After binding, all API calls use device-signed ES256 JWTs. Password auth does **not** return access tokens, refresh tokens, or session cookies.

## Database

Zero-config default: SQLite is used if no database URL is provided.

To use Postgres (recommended for production):

```
H4CKATH0N_DATABASE_URL=postgresql+psycopg://user:pass@host:5432/dbname
```

The `psycopg[binary]` driver is included by default – no extra install needed.

## LLM

h4ckath0n includes LLM tooling by default. Set `OPENAI_API_KEY` and use:

```python
from h4ckath0n.llm import llm

client = llm()
resp = client.chat(
    system="You are a helpful assistant.",
    user="Summarize this in one sentence: ...",
)
print(resp.text)
```

Fails gracefully with a clear error message when `OPENAI_API_KEY` is not set.

## Configuration

Everything is environment-driven (prefix `H4CKATH0N_`):

| Variable | Default | Description |
|---|---|---|
| `H4CKATH0N_ENV` | `development` | `development` or `production` |
| `H4CKATH0N_DATABASE_URL` | `sqlite:///./h4ckath0n.db` | Database connection string |
| `H4CKATH0N_RP_ID` | `localhost` *(dev only)* | WebAuthn relying party ID (**required in production**) |
| `H4CKATH0N_ORIGIN` | `http://localhost:8000` *(dev only)* | WebAuthn expected origin (**required in production**) |
| `H4CKATH0N_WEBAUTHN_TTL_SECONDS` | `300` | Challenge expiry time (seconds) |
| `H4CKATH0N_USER_VERIFICATION` | `preferred` | WebAuthn user verification requirement |
| `H4CKATH0N_ATTESTATION` | `none` | WebAuthn attestation preference |
| `H4CKATH0N_PASSWORD_AUTH_ENABLED` | `false` | Enable password auth routes (requires `[password]` extra) |
| `H4CKATH0N_BOOTSTRAP_ADMIN_EMAILS` | `[]` | JSON list of emails that get admin role on registration |
| `H4CKATH0N_FIRST_USER_IS_ADMIN` | `false` | First registered user becomes admin (dev convenience) |
| `OPENAI_API_KEY` | — | OpenAI API key for the LLM module |

In development mode, missing WebAuthn settings generate localhost defaults with warnings.
In production mode, missing `RP_ID` and `ORIGIN` cause a hard error.

## Observability (opt-in)

Enable end-to-end tracing across FastAPI requests, LangGraph nodes, tool calls, and LLM calls.

### Enable LangSmith tracing

```
LANGSMITH_TRACING=true
LANGSMITH_API_KEY=...
LANGSMITH_PROJECT=your-project-name
```

### Trace IDs

When observability is enabled, h4ckath0n attaches `X-Trace-Id` to all responses:

```python
from h4ckath0n import create_app
from h4ckath0n.obs import init_observability

app = create_app()
init_observability(app)
```

## Development

```bash
git clone https://github.com/BTreeMap/h4ckath0n.git
cd h4ckath0n
uv sync
uv run pytest
```

Quality gates:

```bash
uv run ruff format --check .
uv run ruff check .
uv run mypy src
uv run pytest
```

Build:

```bash
uv build
```

## License

MIT. See `LICENSE`.
