parbaked 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- parbaked-0.4.0/.gitignore +40 -0
- parbaked-0.4.0/LICENSE +21 -0
- parbaked-0.4.0/PKG-INFO +204 -0
- parbaked-0.4.0/README.md +168 -0
- parbaked-0.4.0/examples/README.md +17 -0
- parbaked-0.4.0/pyproject.toml +84 -0
- parbaked-0.4.0/src/parbaked/__init__.py +20 -0
- parbaked-0.4.0/src/parbaked/admin.py +216 -0
- parbaked-0.4.0/src/parbaked/app.py +234 -0
- parbaked-0.4.0/src/parbaked/auth/__init__.py +12 -0
- parbaked-0.4.0/src/parbaked/auth/deps.py +67 -0
- parbaked-0.4.0/src/parbaked/auth/jwt.py +51 -0
- parbaked-0.4.0/src/parbaked/auth/models.py +54 -0
- parbaked-0.4.0/src/parbaked/auth/passwords.py +18 -0
- parbaked-0.4.0/src/parbaked/auth/router.py +312 -0
- parbaked-0.4.0/src/parbaked/auth/tokens.py +104 -0
- parbaked-0.4.0/src/parbaked/cli/__init__.py +1 -0
- parbaked-0.4.0/src/parbaked/cli/main.py +177 -0
- parbaked-0.4.0/src/parbaked/cli/templates/starter/.env.example +22 -0
- parbaked-0.4.0/src/parbaked/cli/templates/starter/Dockerfile +14 -0
- parbaked-0.4.0/src/parbaked/cli/templates/starter/Makefile +59 -0
- parbaked-0.4.0/src/parbaked/cli/templates/starter/README.md +59 -0
- parbaked-0.4.0/src/parbaked/cli/templates/starter/fly.toml.tpl +33 -0
- parbaked-0.4.0/src/parbaked/cli/templates/starter/gitignore.tpl +17 -0
- parbaked-0.4.0/src/parbaked/cli/templates/starter/main.py +36 -0
- parbaked-0.4.0/src/parbaked/cli/templates/starter/pyproject.toml.tpl +10 -0
- parbaked-0.4.0/src/parbaked/cli/templates/starter/templates/base.html +22 -0
- parbaked-0.4.0/src/parbaked/cli/templates/starter/templates/dashboard.html +40 -0
- parbaked-0.4.0/src/parbaked/cli/templates/starter/templates/home.html +11 -0
- parbaked-0.4.0/src/parbaked/cli/templates/starter/templates/login.html +55 -0
- parbaked-0.4.0/src/parbaked/cli/templates/starter/templates/signup.html +69 -0
- parbaked-0.4.0/src/parbaked/config.py +85 -0
- parbaked-0.4.0/src/parbaked/email/__init__.py +12 -0
- parbaked-0.4.0/src/parbaked/email/base.py +19 -0
- parbaked-0.4.0/src/parbaked/email/console.py +37 -0
- parbaked-0.4.0/src/parbaked/email/smtp.py +37 -0
- parbaked-0.4.0/src/parbaked/email/templates.py +92 -0
- parbaked-0.4.0/src/parbaked/frontend/LoginForm.tsx +78 -0
- parbaked-0.4.0/src/parbaked/frontend/PendingScreen.tsx +15 -0
- parbaked-0.4.0/src/parbaked/frontend/README.md +32 -0
- parbaked-0.4.0/src/parbaked/frontend/SignupForm.tsx +105 -0
- parbaked-0.4.0/src/parbaked/ratelimit.py +33 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
*.egg
|
|
9
|
+
|
|
10
|
+
# Virtual environments
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
env/
|
|
14
|
+
|
|
15
|
+
# Testing / coverage
|
|
16
|
+
.pytest_cache/
|
|
17
|
+
.coverage
|
|
18
|
+
htmlcov/
|
|
19
|
+
.tox/
|
|
20
|
+
.mypy_cache/
|
|
21
|
+
.ruff_cache/
|
|
22
|
+
|
|
23
|
+
# IDE
|
|
24
|
+
.idea/
|
|
25
|
+
.vscode/
|
|
26
|
+
*.swp
|
|
27
|
+
*.swo
|
|
28
|
+
|
|
29
|
+
# Env / secrets
|
|
30
|
+
.env
|
|
31
|
+
.env.local
|
|
32
|
+
*.db
|
|
33
|
+
*.sqlite
|
|
34
|
+
|
|
35
|
+
# OS
|
|
36
|
+
.DS_Store
|
|
37
|
+
Thumbs.db
|
|
38
|
+
|
|
39
|
+
# Build artifacts
|
|
40
|
+
*.log
|
parbaked-0.4.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sam Leighton
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
parbaked-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: parbaked
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: A par-baked starter for PoC apps: signup-with-approval auth, rate limiting, and (soon) deploy scaffolding for fly.io
|
|
5
|
+
Project-URL: Homepage, https://github.com/saml7n/parbaked
|
|
6
|
+
Project-URL: Repository, https://github.com/saml7n/parbaked
|
|
7
|
+
Project-URL: Issues, https://github.com/saml7n/parbaked/issues
|
|
8
|
+
Author: Sam Leighton
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: auth,fastapi,fly.io,poc,scaffold,starter
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Framework :: FastAPI
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: bcrypt>=4.1
|
|
21
|
+
Requires-Dist: email-validator>=2.1
|
|
22
|
+
Requires-Dist: fastapi>=0.110
|
|
23
|
+
Requires-Dist: jinja2>=3.1
|
|
24
|
+
Requires-Dist: pydantic-settings>=2.1
|
|
25
|
+
Requires-Dist: pydantic>=2.5
|
|
26
|
+
Requires-Dist: pyjwt>=2.8
|
|
27
|
+
Requires-Dist: slowapi>=0.1.9
|
|
28
|
+
Requires-Dist: sqlmodel>=0.0.16
|
|
29
|
+
Requires-Dist: typer>=0.12
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# parbaked
|
|
38
|
+
|
|
39
|
+
**A par-baked starter for FastAPI PoCs.** Signup with admin approval, rate limiting, and an admin dashboard — set up in one line.
|
|
40
|
+
|
|
41
|
+
> 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."
|
|
42
|
+
|
|
43
|
+
## 30-second quickstart
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uvx parbaked new myapp
|
|
47
|
+
cd myapp
|
|
48
|
+
make dev
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
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.
|
|
52
|
+
|
|
53
|
+
You now have a working PoC with:
|
|
54
|
+
|
|
55
|
+
- Signup + login + JWT sessions
|
|
56
|
+
- Admin-approval gate (nobody gets in until you click approve)
|
|
57
|
+
- Built-in admin dashboard with pending/active/rejected queues
|
|
58
|
+
- Per-IP rate limiting (5/min signup, 10/min login by default)
|
|
59
|
+
- SQLite DB and secrets auto-created on first run
|
|
60
|
+
|
|
61
|
+
## Add to an existing FastAPI app
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from fastapi import FastAPI
|
|
65
|
+
from parbaked import Parbaked
|
|
66
|
+
|
|
67
|
+
app = FastAPI()
|
|
68
|
+
parbaked = Parbaked(app) # ← that's the whole setup
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
That gives you `/auth/signup`, `/auth/login`, `/auth/me`, `/auth/admin`, and approval magic links. On first boot you'll see:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
╔════════════════════════════════════════════════════════════════╗
|
|
75
|
+
║ parbaked v0.2.0 is running ║
|
|
76
|
+
╠════════════════════════════════════════════════════════════════╣
|
|
77
|
+
║ Sign up at: http://localhost:8000/auth/signup ║
|
|
78
|
+
║ Admin panel: http://localhost:8000/auth/admin ║
|
|
79
|
+
║ Admin user: admin ║
|
|
80
|
+
║ Admin pass: z703EwDKmEKL9S6SaO39uuRq ║
|
|
81
|
+
║ Email out: Console (printed below) ║
|
|
82
|
+
║ Database: sqlite:///./parbaked.db ║
|
|
83
|
+
║ ║
|
|
84
|
+
║ ⚠ Auto-generated secrets stored in .parbaked.json ║
|
|
85
|
+
║ Add it to .gitignore. For prod, set env vars instead. ║
|
|
86
|
+
╚════════════════════════════════════════════════════════════════╝
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Protect your own routes:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from fastapi import Depends
|
|
93
|
+
|
|
94
|
+
@app.get("/profile")
|
|
95
|
+
def profile(user = Depends(parbaked.current_user)):
|
|
96
|
+
return {"email": user.email, "name": user.name}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Why an approval gate?
|
|
100
|
+
|
|
101
|
+
Without one:
|
|
102
|
+
|
|
103
|
+
- Anyone on the internet can sign up to your PoC
|
|
104
|
+
- Even with rate limits, sustained traffic can autoscale you onto a bill
|
|
105
|
+
- You can't show it to a friend without also showing it to bots
|
|
106
|
+
|
|
107
|
+
With one:
|
|
108
|
+
|
|
109
|
+
- Pending accounts can't log in. Period.
|
|
110
|
+
- You see every signup. Click approve in your inbox or in the dashboard.
|
|
111
|
+
- Bots can fill the database with junk, but they can't *do* anything — and rate limits cap the junk.
|
|
112
|
+
|
|
113
|
+
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.
|
|
114
|
+
|
|
115
|
+
## Configuration
|
|
116
|
+
|
|
117
|
+
Everything has sensible defaults. Override via env vars (prefix `PARBAKED_`) or by passing args to `Parbaked()`:
|
|
118
|
+
|
|
119
|
+
| Env var | Default | What it does |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `JWT_SECRET` | *(auto-generated)* | Session token signing key |
|
|
122
|
+
| `APPROVAL_TOKEN_SECRET` | *(auto-generated)* | Magic-link signing key |
|
|
123
|
+
| `ADMIN_PASSWORD` | *(auto-generated)* | Dashboard login password (user is always `admin`) |
|
|
124
|
+
| `ADMIN_EMAIL` | unset | Where signup-approval emails go (dashboard works without this) |
|
|
125
|
+
| `APP_NAME` | `"My App"` | Used in email subjects |
|
|
126
|
+
| `APP_URL` | `http://localhost:8000` | Public URL for magic links |
|
|
127
|
+
| `EMAIL_TRANSPORT` | `console` | `console` (dev) or `smtp` |
|
|
128
|
+
| `SMTP_HOST` / `_PORT` / `_USER` / `_PASSWORD` / `_FROM` | — | SMTP credentials if transport is `smtp` |
|
|
129
|
+
| `RATELIMIT_SIGNUP` | `5/minute` | Per-IP signup limit |
|
|
130
|
+
| `RATELIMIT_LOGIN` | `10/minute` | Per-IP login limit |
|
|
131
|
+
| `DATABASE_URL` | `sqlite:///./parbaked.db` | Standard SQLAlchemy URL |
|
|
132
|
+
|
|
133
|
+
Auto-generated secrets get persisted to `.parbaked.json` (chmod 600). In production, set them as env vars instead.
|
|
134
|
+
|
|
135
|
+
## Security posture
|
|
136
|
+
|
|
137
|
+
What protects you from a bill:
|
|
138
|
+
|
|
139
|
+
- **Per-IP rate limits** on signup and login (slowapi)
|
|
140
|
+
- **No email enumeration** — signup with an existing email and login with a wrong password return generic errors
|
|
141
|
+
- **Approval gate** — even if someone gets past the rate limit, they can't do anything until you click approve
|
|
142
|
+
- **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)
|
|
143
|
+
|
|
144
|
+
What's on the roadmap (and isn't here yet):
|
|
145
|
+
|
|
146
|
+
- **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).
|
|
147
|
+
- **v0.4** — Pluggable CAPTCHA on signup (Cloudflare Turnstile / hCaptcha).
|
|
148
|
+
- **v0.5** — Resend / SendGrid / SES email transports.
|
|
149
|
+
|
|
150
|
+
## React/TypeScript components
|
|
151
|
+
|
|
152
|
+
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/`:
|
|
153
|
+
|
|
154
|
+
- `SignupForm.tsx`
|
|
155
|
+
- `LoginForm.tsx`
|
|
156
|
+
- `PendingScreen.tsx`
|
|
157
|
+
|
|
158
|
+
Tailwind classes, no internal dependencies. See `src/parbaked/frontend/README.md`.
|
|
159
|
+
|
|
160
|
+
## When you need more control
|
|
161
|
+
|
|
162
|
+
The one-call `Parbaked(app)` is the simple case. If you want to wire pieces yourself:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from parbaked import ParbakedConfig
|
|
166
|
+
from parbaked.auth import build_auth_router, make_current_user
|
|
167
|
+
from parbaked.email import ConsoleEmail
|
|
168
|
+
from parbaked.ratelimit import install_rate_limiting
|
|
169
|
+
|
|
170
|
+
config = ParbakedConfig(jwt_secret="...", admin_email="you@example.com")
|
|
171
|
+
limiter = install_rate_limiting(app, config)
|
|
172
|
+
app.include_router(
|
|
173
|
+
build_auth_router(config, get_session, ConsoleEmail(), limiter=limiter)
|
|
174
|
+
)
|
|
175
|
+
current_user = make_current_user(config, get_session)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
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.
|
|
179
|
+
|
|
180
|
+
## Install
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
pip install parbaked
|
|
184
|
+
# or
|
|
185
|
+
uv add parbaked
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Requires Python 3.11+.
|
|
189
|
+
|
|
190
|
+
## Develop / contribute
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
git clone https://github.com/saml7n/parbaked
|
|
194
|
+
cd parbaked
|
|
195
|
+
uv venv && source .venv/bin/activate
|
|
196
|
+
uv pip install -e ".[dev]"
|
|
197
|
+
pytest
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Issues and PRs welcome.
|
|
201
|
+
|
|
202
|
+
## License
|
|
203
|
+
|
|
204
|
+
MIT — see [LICENSE](LICENSE).
|
parbaked-0.4.0/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# parbaked
|
|
2
|
+
|
|
3
|
+
**A par-baked starter for FastAPI PoCs.** Signup with admin approval, rate limiting, and an admin dashboard — set up in one line.
|
|
4
|
+
|
|
5
|
+
> 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."
|
|
6
|
+
|
|
7
|
+
## 30-second quickstart
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uvx parbaked new myapp
|
|
11
|
+
cd myapp
|
|
12
|
+
make dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
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.
|
|
16
|
+
|
|
17
|
+
You now have a working PoC with:
|
|
18
|
+
|
|
19
|
+
- Signup + login + JWT sessions
|
|
20
|
+
- Admin-approval gate (nobody gets in until you click approve)
|
|
21
|
+
- Built-in admin dashboard with pending/active/rejected queues
|
|
22
|
+
- Per-IP rate limiting (5/min signup, 10/min login by default)
|
|
23
|
+
- SQLite DB and secrets auto-created on first run
|
|
24
|
+
|
|
25
|
+
## Add to an existing FastAPI app
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from fastapi import FastAPI
|
|
29
|
+
from parbaked import Parbaked
|
|
30
|
+
|
|
31
|
+
app = FastAPI()
|
|
32
|
+
parbaked = Parbaked(app) # ← that's the whole setup
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
That gives you `/auth/signup`, `/auth/login`, `/auth/me`, `/auth/admin`, and approval magic links. On first boot you'll see:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
╔════════════════════════════════════════════════════════════════╗
|
|
39
|
+
║ parbaked v0.2.0 is running ║
|
|
40
|
+
╠════════════════════════════════════════════════════════════════╣
|
|
41
|
+
║ Sign up at: http://localhost:8000/auth/signup ║
|
|
42
|
+
║ Admin panel: http://localhost:8000/auth/admin ║
|
|
43
|
+
║ Admin user: admin ║
|
|
44
|
+
║ Admin pass: z703EwDKmEKL9S6SaO39uuRq ║
|
|
45
|
+
║ Email out: Console (printed below) ║
|
|
46
|
+
║ Database: sqlite:///./parbaked.db ║
|
|
47
|
+
║ ║
|
|
48
|
+
║ ⚠ Auto-generated secrets stored in .parbaked.json ║
|
|
49
|
+
║ Add it to .gitignore. For prod, set env vars instead. ║
|
|
50
|
+
╚════════════════════════════════════════════════════════════════╝
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Protect your own routes:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from fastapi import Depends
|
|
57
|
+
|
|
58
|
+
@app.get("/profile")
|
|
59
|
+
def profile(user = Depends(parbaked.current_user)):
|
|
60
|
+
return {"email": user.email, "name": user.name}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Why an approval gate?
|
|
64
|
+
|
|
65
|
+
Without one:
|
|
66
|
+
|
|
67
|
+
- Anyone on the internet can sign up to your PoC
|
|
68
|
+
- Even with rate limits, sustained traffic can autoscale you onto a bill
|
|
69
|
+
- You can't show it to a friend without also showing it to bots
|
|
70
|
+
|
|
71
|
+
With one:
|
|
72
|
+
|
|
73
|
+
- Pending accounts can't log in. Period.
|
|
74
|
+
- You see every signup. Click approve in your inbox or in the dashboard.
|
|
75
|
+
- Bots can fill the database with junk, but they can't *do* anything — and rate limits cap the junk.
|
|
76
|
+
|
|
77
|
+
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.
|
|
78
|
+
|
|
79
|
+
## Configuration
|
|
80
|
+
|
|
81
|
+
Everything has sensible defaults. Override via env vars (prefix `PARBAKED_`) or by passing args to `Parbaked()`:
|
|
82
|
+
|
|
83
|
+
| Env var | Default | What it does |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| `JWT_SECRET` | *(auto-generated)* | Session token signing key |
|
|
86
|
+
| `APPROVAL_TOKEN_SECRET` | *(auto-generated)* | Magic-link signing key |
|
|
87
|
+
| `ADMIN_PASSWORD` | *(auto-generated)* | Dashboard login password (user is always `admin`) |
|
|
88
|
+
| `ADMIN_EMAIL` | unset | Where signup-approval emails go (dashboard works without this) |
|
|
89
|
+
| `APP_NAME` | `"My App"` | Used in email subjects |
|
|
90
|
+
| `APP_URL` | `http://localhost:8000` | Public URL for magic links |
|
|
91
|
+
| `EMAIL_TRANSPORT` | `console` | `console` (dev) or `smtp` |
|
|
92
|
+
| `SMTP_HOST` / `_PORT` / `_USER` / `_PASSWORD` / `_FROM` | — | SMTP credentials if transport is `smtp` |
|
|
93
|
+
| `RATELIMIT_SIGNUP` | `5/minute` | Per-IP signup limit |
|
|
94
|
+
| `RATELIMIT_LOGIN` | `10/minute` | Per-IP login limit |
|
|
95
|
+
| `DATABASE_URL` | `sqlite:///./parbaked.db` | Standard SQLAlchemy URL |
|
|
96
|
+
|
|
97
|
+
Auto-generated secrets get persisted to `.parbaked.json` (chmod 600). In production, set them as env vars instead.
|
|
98
|
+
|
|
99
|
+
## Security posture
|
|
100
|
+
|
|
101
|
+
What protects you from a bill:
|
|
102
|
+
|
|
103
|
+
- **Per-IP rate limits** on signup and login (slowapi)
|
|
104
|
+
- **No email enumeration** — signup with an existing email and login with a wrong password return generic errors
|
|
105
|
+
- **Approval gate** — even if someone gets past the rate limit, they can't do anything until you click approve
|
|
106
|
+
- **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)
|
|
107
|
+
|
|
108
|
+
What's on the roadmap (and isn't here yet):
|
|
109
|
+
|
|
110
|
+
- **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).
|
|
111
|
+
- **v0.4** — Pluggable CAPTCHA on signup (Cloudflare Turnstile / hCaptcha).
|
|
112
|
+
- **v0.5** — Resend / SendGrid / SES email transports.
|
|
113
|
+
|
|
114
|
+
## React/TypeScript components
|
|
115
|
+
|
|
116
|
+
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/`:
|
|
117
|
+
|
|
118
|
+
- `SignupForm.tsx`
|
|
119
|
+
- `LoginForm.tsx`
|
|
120
|
+
- `PendingScreen.tsx`
|
|
121
|
+
|
|
122
|
+
Tailwind classes, no internal dependencies. See `src/parbaked/frontend/README.md`.
|
|
123
|
+
|
|
124
|
+
## When you need more control
|
|
125
|
+
|
|
126
|
+
The one-call `Parbaked(app)` is the simple case. If you want to wire pieces yourself:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from parbaked import ParbakedConfig
|
|
130
|
+
from parbaked.auth import build_auth_router, make_current_user
|
|
131
|
+
from parbaked.email import ConsoleEmail
|
|
132
|
+
from parbaked.ratelimit import install_rate_limiting
|
|
133
|
+
|
|
134
|
+
config = ParbakedConfig(jwt_secret="...", admin_email="you@example.com")
|
|
135
|
+
limiter = install_rate_limiting(app, config)
|
|
136
|
+
app.include_router(
|
|
137
|
+
build_auth_router(config, get_session, ConsoleEmail(), limiter=limiter)
|
|
138
|
+
)
|
|
139
|
+
current_user = make_current_user(config, get_session)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
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.
|
|
143
|
+
|
|
144
|
+
## Install
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pip install parbaked
|
|
148
|
+
# or
|
|
149
|
+
uv add parbaked
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Requires Python 3.11+.
|
|
153
|
+
|
|
154
|
+
## Develop / contribute
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
git clone https://github.com/saml7n/parbaked
|
|
158
|
+
cd parbaked
|
|
159
|
+
uv venv && source .venv/bin/activate
|
|
160
|
+
uv pip install -e ".[dev]"
|
|
161
|
+
pytest
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Issues and PRs welcome.
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# parbaked examples
|
|
2
|
+
|
|
3
|
+
Runnable, version-pinned examples. Each file is a complete FastAPI app — copy, paste, run.
|
|
4
|
+
|
|
5
|
+
Run any of them with:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv run uvicorn examples.<filename>:app --reload
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
| File | What it shows |
|
|
12
|
+
|---|---|
|
|
13
|
+
| [`minimal.py`](minimal.py) | The 4-line setup. Zero config. |
|
|
14
|
+
| [`with_smtp.py`](with_smtp.py) | Real email via SMTP (Resend / SES / Gmail / anything). |
|
|
15
|
+
| [`protected_routes.py`](protected_routes.py) | Using `parbaked.current_user` to gate your own routes. |
|
|
16
|
+
| [`per_user_data.py`](per_user_data.py) | Adding a per-user table that joins on `users.id` — the right way to extend the schema. |
|
|
17
|
+
| [`custom_emails.py`](custom_emails.py) | Overriding the email body copy without forking parbaked. |
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "parbaked"
|
|
3
|
+
version = "0.4.0"
|
|
4
|
+
description = "A par-baked starter for PoC apps: signup-with-approval auth, rate limiting, and (soon) deploy scaffolding for fly.io"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
authors = [{ name = "Sam Leighton" }]
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
keywords = ["fastapi", "auth", "starter", "scaffold", "fly.io", "poc"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 3 - Alpha",
|
|
12
|
+
"Framework :: FastAPI",
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Programming Language :: Python :: 3.11",
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
17
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"fastapi>=0.110",
|
|
21
|
+
"sqlmodel>=0.0.16",
|
|
22
|
+
"pydantic>=2.5",
|
|
23
|
+
"pydantic-settings>=2.1",
|
|
24
|
+
"bcrypt>=4.1",
|
|
25
|
+
"pyjwt>=2.8",
|
|
26
|
+
"slowapi>=0.1.9",
|
|
27
|
+
"email-validator>=2.1",
|
|
28
|
+
"typer>=0.12",
|
|
29
|
+
"jinja2>=3.1",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=8.0",
|
|
35
|
+
"pytest-asyncio>=0.23",
|
|
36
|
+
"httpx>=0.27",
|
|
37
|
+
"ruff>=0.4",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.scripts]
|
|
41
|
+
parbaked = "parbaked.cli.main:app"
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://github.com/saml7n/parbaked"
|
|
45
|
+
Repository = "https://github.com/saml7n/parbaked"
|
|
46
|
+
Issues = "https://github.com/saml7n/parbaked/issues"
|
|
47
|
+
|
|
48
|
+
[build-system]
|
|
49
|
+
requires = ["hatchling"]
|
|
50
|
+
build-backend = "hatchling.build"
|
|
51
|
+
|
|
52
|
+
[tool.hatch.build.targets.wheel]
|
|
53
|
+
packages = ["src/parbaked"]
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.sdist]
|
|
56
|
+
include = ["src/parbaked", "README.md", "LICENSE"]
|
|
57
|
+
|
|
58
|
+
[tool.pytest.ini_options]
|
|
59
|
+
testpaths = ["tests"]
|
|
60
|
+
asyncio_mode = "auto"
|
|
61
|
+
|
|
62
|
+
[tool.ruff]
|
|
63
|
+
line-length = 100
|
|
64
|
+
target-version = "py311"
|
|
65
|
+
|
|
66
|
+
[tool.ruff.lint]
|
|
67
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
68
|
+
extend-safe-fixes = ["UP"]
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint.flake8-bugbear]
|
|
71
|
+
# These callables in default arguments are the canonical patterns for FastAPI
|
|
72
|
+
# / Typer and are intentional, not a B008 bug.
|
|
73
|
+
extend-immutable-calls = [
|
|
74
|
+
"fastapi.Depends",
|
|
75
|
+
"fastapi.Header",
|
|
76
|
+
"fastapi.Query",
|
|
77
|
+
"fastapi.Cookie",
|
|
78
|
+
"fastapi.Body",
|
|
79
|
+
"fastapi.Path",
|
|
80
|
+
"fastapi.File",
|
|
81
|
+
"fastapi.Form",
|
|
82
|
+
"typer.Argument",
|
|
83
|
+
"typer.Option",
|
|
84
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""parbaked — a par-baked starter for PoC apps.
|
|
2
|
+
|
|
3
|
+
Public API re-exports the most commonly used pieces so consumers can do:
|
|
4
|
+
|
|
5
|
+
from parbaked import ParbakedConfig, auth_router, get_current_user
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.4.0"
|
|
9
|
+
|
|
10
|
+
from parbaked.app import Parbaked
|
|
11
|
+
from parbaked.auth.deps import make_current_user
|
|
12
|
+
from parbaked.auth.router import build_auth_router
|
|
13
|
+
from parbaked.config import ParbakedConfig
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"Parbaked",
|
|
17
|
+
"ParbakedConfig",
|
|
18
|
+
"build_auth_router",
|
|
19
|
+
"make_current_user",
|
|
20
|
+
]
|