Add phase 1: Docker setup, auth with invitations and roles
- Django 5.2 + PostgreSQL 17 via docker compose (app + db, volumes, healthcheck on /healthz, test profile running pytest against Postgres) - Custom e-mail user model with user/admin roles - Invitation-only registration with single-use, hashed, expiring links - Admin area: invitations, role changes, deactivation, password reset via one-time link or temporary password (forced change on login) - First admin created from ADMIN_EMAIL/ADMIN_PASSWORD on startup - Login rate limiting per e-mail and IP (DB-backed, worker-safe) - Proxy middleware honouring X-Forwarded-* only from TRUSTED_PROXIES, optional CLIENT_IP_HEADER (e.g. CF-Connecting-IP) - Security headers (CSP, HSTS, Permissions-Policy), secure __Host- cookies - Mobile-first German UI, CLAUDE.md, README, init-env script - 65 tests for auth, permissions, proxy handling and rate limiting Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
# Kochbuch – Notes for Claude
|
||||
|
||||
Self-hosted recipe web app for a homelab. Runs via docker compose behind the owner's existing
|
||||
Nginx + Cloudflare (public URL: https://kochbuch.dammann.vip). The app itself serves plain HTTP on one
|
||||
port; TLS, domain and proxy are out of scope.
|
||||
|
||||
## Status
|
||||
- [x] Phase 1: Docker setup, DB, auth with invitations, roles
|
||||
- [ ] Phase 2: Recipes CRUD, visibility (private/public), photos, notes, search, favourites
|
||||
- [ ] Phase 3: Comments, serving calculator, cooking mode (wake lock)
|
||||
- [ ] Phase 4: AI (OpenAI): clean up manual input + photo/screenshot import
|
||||
- [ ] Phase 5: Link import (schema.org JSON-LD, Instagram with paste fallback, Android PWA share target)
|
||||
- [ ] Phase 6: Backup script, full README
|
||||
|
||||
## Stack
|
||||
Django 5.2 LTS (Python 3.12), server-rendered templates, HTMX planned for Phase 2+, PostgreSQL 17,
|
||||
gunicorn (gthread), WhiteNoise for static files. No Redis, no JS build step.
|
||||
|
||||
## Layout
|
||||
- `config/` – settings (all env-driven, see `.env.example`), urls, wsgi. `settings_test.py` for pytest.
|
||||
- `core/` – cross-cutting: `ProxyMiddleware` (trusted proxies, client IP), `SecurityHeadersMiddleware`
|
||||
(CSP, Permissions-Policy, no-store for logged-in pages), DB-backed rate limiter (`core/ratelimit.py`),
|
||||
`/healthz`, error pages.
|
||||
- `accounts/` – custom `User` (e-mail login, `role` = user|admin), `Invitation`, `PasswordResetToken`,
|
||||
login/registration/reset views, admin area (`admin_views.py`, `/verwaltung/…`),
|
||||
`ensure_admin` management command (first admin from env).
|
||||
- `templates/`, `static/` – global templates, `app.css` (mobile first, dark mode), `app.js`.
|
||||
- `docker/entrypoint.sh` – migrate → ensure_admin → purge_ratelimits → gunicorn.
|
||||
- `scripts/` – `init-env.sh` (creates .env with secrets), `dev_server.py` (local run without Docker).
|
||||
|
||||
## Commands
|
||||
```bash
|
||||
# Local (Windows dev box: venv lives at C:\Users\kyled\.venvs\kochbuch due to path-length issues)
|
||||
python -m pytest # tests (SQLite)
|
||||
python scripts/dev_server.py # http://localhost:8000, [email protected]
|
||||
SECRET_KEY=dev python manage.py makemigrations
|
||||
|
||||
# Docker
|
||||
./scripts/init-env.sh [email protected] # once
|
||||
docker compose up -d --build
|
||||
docker compose --profile test run --rm test # tests against Postgres
|
||||
docker compose logs -f app
|
||||
```
|
||||
|
||||
## Conventions
|
||||
- Code, comments, commits: English. UI, README, user-facing messages: German (informal "du").
|
||||
- **Default deny:** `LoginRequiredMiddleware` is active; public views need `@login_not_required`.
|
||||
Admin views use `admin_required` (403). Private content that a user may not see → 404, never 403
|
||||
(don't leak existence).
|
||||
- **Visibility (Phase 2+):** every recipe query for a user goes through one central queryset method
|
||||
(`visible_to(user)`), including list, detail, search, tags, images, comments. Uploaded files are never
|
||||
served from a static path – only through permission-checked views. Tests must cover each access path.
|
||||
- Tags are stored lowercase.
|
||||
- One-time tokens (invitations, resets) are stored only as SHA-256 hashes; the link is shown once.
|
||||
- Rate limits use `core.ratelimit.hit/is_limited`; limit 0 = unlimited. AI limits (Phase 4) must exist
|
||||
but default to 0 (owner's request), configurable via env.
|
||||
- Forwarded headers are trusted only from `TRUSTED_PROXIES`; gunicorn does not trust any itself.
|
||||
- CSP is strict (`script-src 'self'`, `style-src 'self'`): no inline scripts/styles/handlers in templates.
|
||||
- AI output is always an editable draft, never saved directly. `OPENAI_API_KEY` only via env.
|
||||
- Keep it lean: small server, no heavy dependencies without reason.
|
||||
Reference in New Issue
Block a user