A clean, minimalist article reader and blog platform inspired by Medium. Built with FastAPI, Pydantic v2, and a zero-trust security architecture.
writerflow/
├── app/
│ ├── __main__.py # Application factory + lifespan + middleware
│ ├── core/ # Config, JWT auth, CSRF, HTML rendering, IP resolution
│ │ ├── config.py # Environment-enforced settings (_require_env)
│ │ ├── security.py # RS256 JWT, Argon2/PBKDF2 hashing, CSRF tokens
│ │ ├── real_ip.py # Trusted proxy IP resolution (drops private IPs)
│ │ └── html_renderer.py # XSS-safe parametric template engine
│ ├── models/ # Domain entities (frozen dataclasses)
│ │ ├── entities.py # Author, Category, Post
│ │ └── enums.py # PostStatus
│ ├── schemas/ # Pydantic v2 request/response validators
│ │ ├── posts.py # Create/Update/CreateResponse with field-level validation
│ │ ├── authors.py # AuthorResponse
│ │ └── categories.py # CategoryResponse
│ ├── services/ # Business logic layer (stateless)
│ │ ├── base.py # DatabaseManager — SQLite persistence + row mapping
│ │ ├── post_service.py # CRUD, listing, search, view tracking
│ │ ├── author_service.py # Author lookup & listing
│ │ └── category_service.py# Category lookup & listing
│ └── routes/ # HTTP route handlers (one responsibility each)
│ ├── public_routes.py # Home, post detail, categories, authors, search
│ ├── admin_routes.py # Auth-gated dashboard + CRUD with CSRF guards
│ └── api_routes.py # Public JSON API endpoints
├── tests/ # pytest suite (48+ test cases)
│ ├── test_post_service.py # CRUD, filtering, pagination, tags, analytics
│ ├── test_security.py # JWT roundtrip/expiry, password hashing, CSRF, IP resolution
│ ├── test_api_integration.py # End-to-end route testing with TestClient
│ └── test_static_assets.py # Security headers, CSP, input sanitization
├── docs/
│ └── HALLUCINAUT_SECURITY_REPORT.md
├── main.py # Entry point (shim for `python -m app`)
├── requirements.txt
├── .env.example
└── .gitignore
pip install -r requirements.txt --break-system-packagescp .env.example .env
# Edit .env — set a strong SECRET_KEYRequired: SECRET_KEY (minimum 32 bytes). The app will refuse to start without it.
python -m app.__main__
# or
uvicorn app.__main__:app --reloadThe platform is available at http://localhost:8000.
All admin routes (/admin, /admin/posts, etc.) require the header:
X-Admin-Token: <valid_RS256_JWT_token>
Generate a token using the security utilities:
from app.core.security import encode_token
token = encode_token({"sub": "admin", "role": "superuser"})| Method | Path | Description |
|---|---|---|
| GET | / |
Blog feed (paginated, filterable by ?category= or ?author=) |
| GET | /post/{slug} |
Article detail with view tracking |
| GET | /category/{slug} |
Category listing |
| GET | /author/{slug} |
Author profile |
| Method | Path | Description |
|---|---|---|
| GET | /api/posts |
List published posts (?limit=20&offset=0) |
| GET | /api/posts/{slug} |
Single post by slug |
| GET | /api/categories |
All categories |
| GET | /api/authors |
All authors |
| GET | /api/search |
Search posts (?query=...&limit=10) |
| Method | Path | Description |
|---|---|---|
| GET | /admin |
Dashboard with all posts |
| POST | /admin/posts |
Create a new post (requires CSRF token) |
| POST | /admin/posts/{id}/delete |
Delete a post (requires CSRF token) |
| GET | /api/admin/posts |
Admin API — list all posts including drafts |
| Method | Path | Description |
|---|---|---|
| GET | /health |
{ "status": "healthy", "service": "writerflow" } |
- XSS Prevention — All user values HTML-escaped via
html.escape()before embedding in markup or JS literals - JWT Authentication (RS256) — Admin routes require valid signed token with
exp,iat,typeclaims - CSRF Protection — Tokens generated via
secrets.token_hex(32), stored in cookies, validated withhmac.compare_digest() - CORS Lockdown — Origins explicitly configured via
CORS_ORIGINSenv var (no wildcards) - Security Headers — CSP, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy applied to all responses
- Trusted IP Resolution — Loopback/private IPs stripped from
X-Forwarded-Forchain - Pydantic Validation — Field-level validation on all POST/PUT bodies (length limits, status enum, tag sanitization)
- Parameterized Queries — All SQL uses parameterized statements (no string interpolation)
- Insecure Defaults Blocked — App refuses to start without
SECRET_KEYenvironment variable - Password Hashing — Argon2id via passlib (PBKDF2-SHA512 fallback at 600k iterations)
pip install pytest --break-system-packages
cd writerflow
python -m pytest tests/ -vFour test modules, 48+ assertions:
test_post_service.py— 20 tests (CRUD, filtering, pagination, search, analytics)test_security.py— 14 tests (JWT, hashing, CSRF, IP resolution)test_api_integration.py— 9 tests (end-to-end routes, auth guards)test_static_assets.py— 5 tests (security headers, CSP, sanitization)
MIT