Metadata-Version: 2.4
Name: parbaked
Version: 0.9.0
Summary: A par-baked starter for PoC apps: signup-with-approval auth, rate limiting, and deploy scaffolding for fly.io
Project-URL: Homepage, https://github.com/saml7n/parbaked
Project-URL: Repository, https://github.com/saml7n/parbaked
Project-URL: Issues, https://github.com/saml7n/parbaked/issues
Author: Sam Leighton
License: MIT
License-File: LICENSE
Keywords: auth,fastapi,fly.io,poc,scaffold,starter
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: FastAPI
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: >=3.11
Requires-Dist: bcrypt>=4.1
Requires-Dist: email-validator>=2.1
Requires-Dist: fastapi>=0.110
Requires-Dist: jinja2>=3.1
Requires-Dist: pydantic-settings>=2.1
Requires-Dist: pydantic>=2.5
Requires-Dist: pyjwt[crypto]>=2.8
Requires-Dist: python-multipart>=0.0.9
Requires-Dist: slowapi>=0.1.9
Requires-Dist: sqlmodel>=0.0.16
Requires-Dist: typer>=0.12
Provides-Extra: dev
Requires-Dist: httpx>=0.27; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Description-Content-Type: text/markdown

# parbaked

**A par-baked starter for FastAPI PoCs.** Signup with admin approval, rate limiting, and an admin dashboard — set up in one line.

> If you ship PoCs on fly.io, two things eventually bite you: anyone on the internet can spin up accounts in a loop (and run up your bill), and you write the same auth boilerplate every time. parbaked is the slice between "I have an idea" and "this is safe to put online."

## 30-second quickstart

```bash
uvx parbaked new myapp
cd myapp
parbaked dev
```

Open `http://localhost:8000`, sign up. Open `http://localhost:8000/auth/admin` and log in as `admin` with the password printed in your terminal. Approve yourself. Log in. Done.

You now have a working PoC with:

- Signup + login + JWT sessions
- Admin-approval gate (nobody gets in until you click approve)
- Built-in admin dashboard with pending/active/rejected queues
- Per-IP rate limiting (5/min signup, 10/min login by default)
- SQLite DB and secrets auto-created on first run

## Add to an existing FastAPI app

```python
from fastapi import FastAPI
from parbaked import Parbaked

app = FastAPI()
parbaked = Parbaked(app)   # ← that's the whole setup
```

That gives you `/auth/signup`, `/auth/login`, `/auth/me`, `/auth/admin`, and approval magic links. On first boot you'll see:

```
╔════════════════════════════════════════════════════════════════╗
║  parbaked v0.2.0 is running                                    ║
╠════════════════════════════════════════════════════════════════╣
║  Sign up at:   http://localhost:8000/auth/signup               ║
║  Admin panel:  http://localhost:8000/auth/admin                ║
║  Admin user:   admin                                           ║
║  Admin pass:   z703EwDKmEKL9S6SaO39uuRq                        ║
║  Email out:    Console (printed below)                         ║
║  Database:     sqlite:///./parbaked.db                         ║
║                                                                ║
║  ⚠ Auto-generated secrets stored in .parbaked.json             ║
║    Add it to .gitignore. For prod, set env vars instead.       ║
╚════════════════════════════════════════════════════════════════╝
```

Protect your own routes:

```python
from fastapi import Depends

@app.get("/profile")
def profile(user = Depends(parbaked.current_user)):
    return {"email": user.email, "name": user.name}
```

## Why an approval gate?

Without one:

- Anyone on the internet can sign up to your PoC
- Even with rate limits, sustained traffic can autoscale you onto a bill
- You can't show it to a friend without also showing it to bots

With one:

- Pending accounts can't log in. Period.
- You see every signup. Click approve in your inbox or in the dashboard.
- Bots can fill the database with junk, but they can't *do* anything — and rate limits cap the junk.

The approval flow uses **HMAC-signed magic links** (no DB state) AND a **built-in web dashboard** (HTTP-basic-auth gated). Use whichever you prefer — email's nicer for low volume, the dashboard's nicer when you want to see the queue.

## Configuration

Everything has sensible defaults. Override via env vars (prefix `PARBAKED_`) or by passing args to `Parbaked()`:

| Env var | Default | What it does |
|---|---|---|
| `JWT_SECRET` | *(auto-generated)* | Session token signing key |
| `APPROVAL_TOKEN_SECRET` | *(auto-generated)* | Magic-link signing key |
| `ADMIN_PASSWORD` | *(auto-generated)* | Dashboard login password (user is always `admin`) |
| `ADMIN_EMAIL` | unset | Where signup-approval emails go (dashboard works without this) |
| `APP_NAME` | `"My App"` | Used in email subjects |
| `APP_URL` | `http://localhost:8000` | Public URL for magic links |
| `RESEND_KEY` | — | Set this to send real email via Resend. Unset → emails print to stdout. Get a free key (no card) at <https://resend.com/api-keys> or run `parbaked email setup`. |
| `MAIL_FROM` | `onboarding@resend.dev` | From address. Default is Resend's sandbox — no DNS setup, but only delivers to the email you signed up with. For real users, verify your domain at resend.com/domains and set this. |
| `RATELIMIT_SIGNUP` | `5/minute` | Per-IP signup limit |
| `RATELIMIT_LOGIN` | `10/minute` | Per-IP login limit |
| `DATABASE_URL` | `sqlite:///./parbaked.db` | Standard SQLAlchemy URL |

Auto-generated secrets get persisted to `.parbaked.json` (chmod 600). In production, set them as env vars instead.

## Optional: Sign in with Google

parbaked's default path is password + admin approval. No third-party signup needed — that's the point. Google sign-in is **opt-in** and only worth turning on when your users expect to see that button.

When you set both `PARBAKED_GOOGLE_CLIENT_ID` and `PARBAKED_GOOGLE_CLIENT_SECRET`, parbaked exposes `/auth/google/start` and `/auth/google/callback`, and the starter `login.html` renders a "Sign in with Google" button above the password fields. The admin approval gate still applies — Google-authenticated users land in the same pending queue.

You'll need to create an OAuth client in [Google Cloud Console](https://console.cloud.google.com/apis/credentials) and add `https://your-app.example.com/auth/google/callback` as an authorized redirect URI. The parbaked side is just two env vars.

## Security posture

What protects you from a bill:

- **Per-IP rate limits** on signup and login (slowapi)
- **No email enumeration** — signup with an existing email and login with a wrong password return generic errors
- **Approval gate** — even if someone gets past the rate limit, they can't do anything until you click approve
- **bcrypt** for passwords, **HS256 JWT** for sessions, **audience-scoped JWT** for magic links so a session token can never be replayed as approval (and vice versa)

What's on the roadmap (and isn't here yet):

- **v0.3** — Deploy scaffolding: opinionated `Dockerfile`, `fly.toml` with `auto_stop_machines = "stop"` + hard max-machines cap, `make tunnel` recipe using `cloudflared tunnel --url http://localhost:8000` (no auth needed; ephemeral trycloudflare.com URL).
- **v0.4** — Pluggable CAPTCHA on signup (Cloudflare Turnstile / hCaptcha).
- **v0.5** (this release) — Resend as the opinionated prod email path, `parbaked email setup`/`test` CLI, `/health` endpoint, admin/user CLI commands, "write your app here" starter polish, production-database guidance.

## React/TypeScript components

Drop-in components for if you're building a React frontend. They ship as **source** in `src/parbaked/frontend/` — copy them into your `web/src/`:

- `SignupForm.tsx`
- `LoginForm.tsx`
- `PendingScreen.tsx`

Tailwind classes, no internal dependencies. See `src/parbaked/frontend/README.md`.

## When you need more control

The one-call `Parbaked(app)` is the simple case. If you want to wire pieces yourself:

```python
from parbaked import ParbakedConfig
from parbaked.auth import build_auth_router, make_current_user
from parbaked.email import ConsoleEmail
from parbaked.ratelimit import install_rate_limiting

config = ParbakedConfig(jwt_secret="...", admin_email="you@example.com")
limiter = install_rate_limiting(app, config)
app.include_router(
    build_auth_router(config, get_session, ConsoleEmail(), limiter=limiter)
)
current_user = make_current_user(config, get_session)
```

Same building blocks, no smart defaults. Use this when you want a custom email transport, your own admin dashboard, or to wire parbaked into a non-trivial app structure.

## Install

```bash
pip install parbaked
# or
uv add parbaked
```

Requires Python 3.11+.

## Develop / contribute

```bash
git clone https://github.com/saml7n/parbaked
cd parbaked
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"
pytest
```

Issues and PRs welcome.

## License

MIT — see [LICENSE](LICENSE).
