Skip to main content

A par-baked starter for PoC apps: signup-with-approval auth, rate limiting, and (soon) deploy scaffolding for fly.io

Project description

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

uvx parbaked new myapp
cd myapp
make 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

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:

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.

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:

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

pip install parbaked
# or
uv add parbaked

Requires Python 3.11+.

Develop / contribute

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

parbaked-0.5.0.tar.gz (33.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

parbaked-0.5.0-py3-none-any.whl (50.1 kB view details)

Uploaded Python 3

File details

Details for the file parbaked-0.5.0.tar.gz.

File metadata

  • Download URL: parbaked-0.5.0.tar.gz
  • Upload date:
  • Size: 33.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for parbaked-0.5.0.tar.gz
Algorithm Hash digest
SHA256 836d726f27efa2412f195e090aa04b8e8b18b5c04942cdb5f8f13921ef14249f
MD5 672464862cba471cd9053f76e53a5572
BLAKE2b-256 961c0dcf64b62bcbc5fda97126e16437d1c8047d7272a5fb14a52a6ffd537009

See more details on using hashes here.

Provenance

The following attestation bundles were made for parbaked-0.5.0.tar.gz:

Publisher: release.yml on saml7n/parbaked

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file parbaked-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: parbaked-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 50.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for parbaked-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8c9728a8c27d284cf310c24fa8dfcc333d26c7fe43599c986a9fe6ca6cb4f7e5
MD5 f66247c956cd6b5bd796658fdc1a85c8
BLAKE2b-256 53ab007a2bd90f3f0a353b51e9f24273538db0f9391cc4c9c795adc31f6de3a5

See more details on using hashes here.

Provenance

The following attestation bundles were made for parbaked-0.5.0-py3-none-any.whl:

Publisher: release.yml on saml7n/parbaked

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page