Update admin theme/layout and refresh README details.
Align the project baseline with the latest admin interface styling and layout structure while documenting setup and usage updates in README.
This commit is contained in:
@@ -0,0 +1,426 @@
|
||||
# Compton Platform
|
||||
|
||||
Monorepo-lite project that follows the `docs/TZ.md` specification for the Compton platform.
|
||||
|
||||
## Stack
|
||||
|
||||
- **Frontend:** React 19, TypeScript, Vite, React Router, TanStack Query, Zustand, RHF + Zod, Ant Design (admin panel)
|
||||
- **Backend:** FastAPI, SQLAlchemy 2, Alembic, Pydantic v2
|
||||
- **Data/Infra:** PostgreSQL, Redis, MinIO (S3-compatible), Docker Compose
|
||||
- **Quality:** Vitest, Testing Library, Playwright, pytest, coverage gates in CI
|
||||
|
||||
## Repository Layout
|
||||
|
||||
- `apps/web` — frontend SPA
|
||||
- `apps/api` — backend API (`app/core/crypto.py` — unified crypto; `app/core/install_secrets.py` — bootstrap/lock)
|
||||
- `apps/api/data/secrets/` — per-install secrets (`install.env`, gitignored)
|
||||
- `apps/api/data/compton_settings.json` — runtime panel settings (superuser-editable via API)
|
||||
- `packages/shared-types` — generated API types contract target
|
||||
- `packages/eslint-config` — shared eslint config package
|
||||
- `infra` — docker/nginx/ci helper files
|
||||
- `docker-compose.dev-ports.yml` — optional override to expose DB/Redis/MinIO on host
|
||||
|
||||
## Quick Start
|
||||
|
||||
**Full stack in Docker** (API + DB + frontend — no local `pnpm install` required):
|
||||
|
||||
```bash
|
||||
# 1. Bootstrap install secrets — REQUIRED before the first docker compose up
|
||||
python apps/api/scripts/bootstrap_install.py
|
||||
|
||||
# 2. Build and start everything
|
||||
docker compose --profile docker-web up -d --build
|
||||
|
||||
# 3. Verify (Windows PowerShell: use curl.exe, not curl — it is an alias for Invoke-WebRequest)
|
||||
curl.exe http://localhost:8000/api/v1/health
|
||||
```
|
||||
|
||||
Open [http://localhost:5173](http://localhost:5173) — static landing (`index.html`). SPA routes (`/login`, `/admin`, `/profile`, …) are served via `app.html` fallback in Vite dev/preview.
|
||||
|
||||
The `web` container runs Vite with hot-reload; dependencies are installed inside the container automatically.
|
||||
|
||||
Log in as `admin@compton.example` (password `Admin1234` by default) and open `/admin`. See [Seed data](#seed-data) for all demo accounts.
|
||||
|
||||
**Optional** — copy env files if you also run API or frontend locally (hybrid mode):
|
||||
|
||||
```bash
|
||||
cp apps/api/.env.example apps/api/.env
|
||||
cp apps/web/.env.example apps/web/.env
|
||||
```
|
||||
|
||||
> **Important:** Run bootstrap **before** the first `docker compose up`. If Postgres was started without `install.env`, the API will fail with `password authentication failed for user "compton_app"`. Fix: `docker compose --profile docker-web down -v`, then bootstrap + up again. See [Troubleshooting](#troubleshooting).
|
||||
|
||||
**Hybrid setup** (Docker API + local frontend):
|
||||
|
||||
```bash
|
||||
cp apps/api/.env.example apps/api/.env
|
||||
cp apps/web/.env.example apps/web/.env
|
||||
pnpm install
|
||||
python apps/api/scripts/bootstrap_install.py
|
||||
docker compose up -d
|
||||
pnpm --filter web dev
|
||||
```
|
||||
|
||||
## Local Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Docker + Docker Compose** — enough for the full-stack Docker workflow
|
||||
- **Node.js 22+ and pnpm 9+** — only if you run the frontend locally (`pnpm --filter web dev`)
|
||||
- **Python 3.12** — bootstrap script, local API, or tests outside Docker
|
||||
|
||||
### Start services
|
||||
|
||||
| Mode | Command | What runs |
|
||||
| ---- | ------- | --------- |
|
||||
| **Full Docker (recommended)** | `docker compose --profile docker-web up -d --build` | API, Postgres, Redis, MinIO, Vite on `:5173` |
|
||||
| **API + infra only** | `docker compose up -d` | API, Postgres, Redis, MinIO — frontend locally |
|
||||
| **Local API + local frontend** | see [Run applications](#run-applications) | everything on host |
|
||||
|
||||
| Service | URL / Port | Notes |
|
||||
| --------- | ---------------------------------------------- | ----- |
|
||||
| API | [http://localhost:8000](http://localhost:8000) | migrations + seed on startup |
|
||||
| Web (dev) | [http://localhost:5173](http://localhost:5173) | Docker `--profile docker-web` or `pnpm --filter web dev` |
|
||||
| Postgres | internal docker network | host access via `docker-compose.dev-ports.yml` |
|
||||
| Redis | internal docker network | host access via `docker-compose.dev-ports.yml` |
|
||||
| MinIO | internal docker network | host access via `docker-compose.dev-ports.yml` |
|
||||
|
||||
> Do not run Docker `web` and `pnpm --filter web dev` at the same time — both bind port **5173**.
|
||||
|
||||
The Docker `web` container bind-mounts source for live-reload on Windows/macOS (`CHOKIDAR_USEPOLLING=true`); `node_modules` stay isolated inside the container.
|
||||
|
||||
### Install secrets
|
||||
|
||||
Each project copy gets unique runtime secrets generated once and locked forever (prevents accidental rotation and DB credential mismatch).
|
||||
|
||||
| File | Purpose |
|
||||
| ---- | ------- |
|
||||
| `apps/api/data/secrets/install.env` | PostgreSQL, JWT, S3/MinIO credentials (gitignored) |
|
||||
| `apps/api/data/secrets/install.meta.json` | Install ID and lock timestamp (gitignored) |
|
||||
|
||||
**Bootstrap (required before first `docker compose up`):**
|
||||
|
||||
```bash
|
||||
python apps/api/scripts/bootstrap_install.py
|
||||
```
|
||||
|
||||
Generated keys: `POSTGRES_USER`, `POSTGRES_PASSWORD`, `DATABASE_URL`, `JWT_ACCESS_SECRET`, `JWT_REFRESH_PEPPER`, `S3_SECRET_KEY`, `MINIO_ROOT_PASSWORD`.
|
||||
|
||||
Docker Compose passes `install.env` directly into the `api`, `postgres`, and `minio` services — no root `.env` or `--env-file` flag needed.
|
||||
|
||||
- Re-run is safe: existing locked secrets are never overwritten.
|
||||
- API entrypoint also calls `ensure_install_secrets()` on startup (adopts env vars when migrating from an older setup).
|
||||
- **Do not rotate** `POSTGRES_PASSWORD` / JWT secrets after first bootstrap without a coordinated DB migration — see [docs/secrets-recovery.md](docs/secrets-recovery.md).
|
||||
|
||||
**Host access to DB/Redis/MinIO** (DBeaver, pgAdmin, MinIO console):
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.yml -f docker-compose.dev-ports.yml up -d
|
||||
```
|
||||
|
||||
| Exposed port | Service |
|
||||
| ------------ | ------- |
|
||||
| 5432 | PostgreSQL |
|
||||
| 6379 | Redis |
|
||||
| 9000 / 9001 | MinIO API / console |
|
||||
|
||||
**Reveal credentials:** Admin → Security → Install Secrets (superuser only, audited). Supports `database_password`, `jwt_access_secret`, `jwt_refresh_pepper`, `s3_secret_key`.
|
||||
|
||||
### Configure environment
|
||||
|
||||
For hybrid or fully local dev, copy example env files:
|
||||
|
||||
```bash
|
||||
cp apps/api/.env.example apps/api/.env
|
||||
cp apps/web/.env.example apps/web/.env
|
||||
```
|
||||
|
||||
Key variables:
|
||||
|
||||
| Variable | App | Purpose |
|
||||
| ----------------------------------------- | --- | ---------------------------------------------------- |
|
||||
| `DATABASE_URL` | api | PostgreSQL connection string |
|
||||
| `APP_ENV` | api | `development` / `production` startup guards |
|
||||
| `JWT_ACCESS_SECRET`, `JWT_REFRESH_PEPPER` | api | Token signing (32+ bytes) |
|
||||
| `VITE_API_URL` | web | Local dev: `http://localhost:8000`. Docker `web`: `http://api:8000` |
|
||||
| `VITE_USE_API_PROXY` | web | `true` — proxy `/api` through Vite (default in dev) |
|
||||
| `EMAIL_DELIVERY_MODE` | api | `memory` (Docker dev) or `smtp` (real mail) |
|
||||
| `STORAGE_MODE` | api | `s3` (MinIO) or `memory` (tests) |
|
||||
| `S3_*` | api | MinIO/S3 credentials and bucket |
|
||||
| `CORS_ORIGINS` | api | Must include `http://localhost:5173` |
|
||||
| `ENABLE_TEST_ROUTES` | api | `true` only for E2E (email token helper) |
|
||||
| `TRUSTED_PROXY_IPS` | api | Which proxy IPs may set `X-Forwarded-For` |
|
||||
| `ADMIN_INITIAL_PASSWORD` | api | Seed password for superuser admin |
|
||||
| `DEMO_USER_PASSWORD` | api | Seed password for regular demo user |
|
||||
| `DEMO_OPS_PASSWORD` | api | Seed password for ops admin (no superuser) |
|
||||
| `ENABLE_RATE_LIMIT` | api | Rate limiting (required `true` in production) |
|
||||
| `COOKIE_SECURE` | api | HttpOnly refresh cookie `Secure` flag |
|
||||
| `COMPTON_SETTINGS_PATH` | api | Path to runtime settings JSON (default `data/compton_settings.json`) |
|
||||
|
||||
In dev the frontend proxies API requests through Vite (`/api` → backend). Locally the target is `localhost:8000`; in Docker Compose it is the `api` service.
|
||||
|
||||
### Runtime settings (`compton_settings.json`)
|
||||
|
||||
Non-secret runtime toggles live in `apps/api/data/compton_settings.json` and are applied on API startup. Superusers can read/update them via `GET/PATCH /admin/settings`.
|
||||
|
||||
Env vars with the same keys (e.g. `ENABLE_RATE_LIMIT`, `CORS_ORIGINS`) act as **locks** — when set, the corresponding JSON field cannot be changed from the admin panel.
|
||||
|
||||
Typical fields: rate limit, API docs, secure cookie, JWT TTL, CORS origins, SMTP host/port, avatar limits, audit retention.
|
||||
|
||||
### Install dependencies (hybrid / local dev only)
|
||||
|
||||
Skip if you use `docker compose --profile docker-web` — dependencies are installed inside the `web` container.
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pip install -r apps/api/requirements-dev.txt
|
||||
```
|
||||
|
||||
### Run applications
|
||||
|
||||
**Option A — Full Docker stack:**
|
||||
|
||||
```bash
|
||||
python apps/api/scripts/bootstrap_install.py # first run only
|
||||
docker compose --profile docker-web up -d --build
|
||||
```
|
||||
|
||||
**Option B — Docker API + local frontend:**
|
||||
|
||||
```bash
|
||||
python apps/api/scripts/bootstrap_install.py # first run only
|
||||
docker compose up -d
|
||||
pnpm --filter web dev
|
||||
```
|
||||
|
||||
**Option C — Local API + local frontend:**
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
alembic upgrade head
|
||||
uvicorn app.main:app --reload --app-dir .
|
||||
```
|
||||
|
||||
In another terminal:
|
||||
|
||||
```bash
|
||||
pnpm --filter web dev
|
||||
```
|
||||
|
||||
Open [http://localhost:5173](http://localhost:5173).
|
||||
|
||||
### Seed data
|
||||
|
||||
After migrations the database is seeded on every API startup with demo users and CMS pages. Passwords come from env vars (defaults in `.env.example`):
|
||||
|
||||
| Email | Env variable | Default (dev) | Role | Superuser | Access |
|
||||
| ----- | ------------ | --------------- | ---- | --------- | ------ |
|
||||
| `admin@compton.example` | `ADMIN_INITIAL_PASSWORD` | `Admin1234` | `admin` | yes | Full admin + Security/Diagnostics/Install Secrets |
|
||||
| `ops@compton.example` | `DEMO_OPS_PASSWORD` | `OpsAdmin1234` | `admin` | no | Users, Content, Activity (no Security/Diagnostics) |
|
||||
| `user@compton.example` | `DEMO_USER_PASSWORD` | `User1234` | `user` | no | Profile only |
|
||||
|
||||
**Content pages:** `about`, `privacy`, `terms` (published).
|
||||
|
||||
> Change demo passwords via env before first seed in production. Admin-created users must pass the shared password policy (length, complexity, denylist).
|
||||
|
||||
## Frontend Routes
|
||||
|
||||
The project uses a **dual-entry** frontend:
|
||||
|
||||
| Entry | Served at | Purpose |
|
||||
| ----- | --------- | ------- |
|
||||
| `index.html` | `/` | Public marketing landing (static HTML/CSS/JS in `main/`) |
|
||||
| `app.html` | `/login`, `/register`, `/profile`, `/admin`, `/pages/:slug`, … | React SPA (auth, profile, admin, CMS pages) |
|
||||
|
||||
Vite middleware rewrites SPA paths to `app.html` on dev/preview (`vite.main-static.ts`).
|
||||
|
||||
| Path | Page | Access |
|
||||
| ------------------ | --------------------- | ----------------------- |
|
||||
| `/` | Landing (`index.html`)| public |
|
||||
| `/login` | Login | guest |
|
||||
| `/register` | Registration | guest |
|
||||
| `/verify` | Email verification | public |
|
||||
| `/forgot-password` | Password reset request| guest |
|
||||
| `/reset-password` | Set new password | public (with token) |
|
||||
| `/pages/:slug` | CMS page | public (published only) |
|
||||
| `/profile` | Profile CRUD + avatar | authenticated |
|
||||
| `/admin` | Admin panel | role `admin` |
|
||||
|
||||
**Auth model:** one login flow for everyone. The `is_superuser` flag on admin accounts controls access to critical panel sections (Security, Diagnostics, Install Secrets reveal, runtime settings). Regular admins see Users, Content, and Activity only.
|
||||
|
||||
- **Access JWT** — in memory only (Zustand), not in `localStorage` / `sessionStorage`.
|
||||
- **Refresh token** — HttpOnly cookie (`Path=/api/v1/auth`, `SameSite=Lax`), rotated on each refresh.
|
||||
- **Session restore on reload** — `AuthBootstrap` calls a deduplicated `bootstrapSessionRefresh()` when a session hint exists in `sessionStorage` **or** the current path is protected (`/admin`, `/profile`). Guards wait for `bootstrapped` before redirecting.
|
||||
- **After login** — admins go to `/admin`, regular users to `/profile`.
|
||||
|
||||
Admin users can return to the public landing via the **«На сайт»** topbar link (full navigation to `/`, not client-side React routing).
|
||||
|
||||
## API Overview
|
||||
|
||||
Base URL: `http://localhost:8000/api/v1`
|
||||
|
||||
| Area | Endpoints |
|
||||
| ----------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Health** | `GET /health` |
|
||||
| **Auth** | `POST /auth/register`, `/login`, `/logout`, `/refresh`, `/verify-email`, `/forgot-password`, `/reset-password` |
|
||||
| **Profile** | `GET/PATCH /users/me`, `POST /users/me/password`, `POST /users/me/avatar` |
|
||||
| **Content** | `GET /content/pages`, `GET /content/pages/{slug}`, admin: `POST/PATCH/DELETE /content/pages`, `GET /content/pages/manage/all` |
|
||||
| **Admin** | `GET/PATCH /admin/users`, `POST /admin/users`, `PATCH /admin/users/{id}/password`, `GET /admin/stats`, `GET/PATCH /admin/settings`, `GET /admin/diagnostics/report`, `GET /admin/activity-feed`, `POST /admin/ui-activity`, `GET /admin/server-log`, `GET /admin/secrets`, `POST /admin/secrets/reveal` |
|
||||
| **Media** | `GET /media/files/{path}` |
|
||||
|
||||
OpenAPI docs (when `ENABLE_DOCS=true`): [http://localhost:8000/api/v1/docs](http://localhost:8000/api/v1/docs)
|
||||
|
||||
## Database Migrations
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
alembic upgrade head # apply schema
|
||||
alembic downgrade -1 # rollback one revision
|
||||
```
|
||||
|
||||
Tables: `users`, `user_profiles`, `refresh_tokens`, `password_reset_tokens`, `email_verification_tokens`, `content_pages`.
|
||||
|
||||
**Security constraints** (PostgreSQL, migration `20260714_0004`):
|
||||
|
||||
- `CHECK` on `users.role`, `users.status`, `content_pages.status`
|
||||
- Superuser rule: `is_superuser=true` only when `role=admin`
|
||||
- Indexes on `expires_at` for token tables
|
||||
|
||||
Expired tokens are cleaned up on API startup (`cleanup_expired_tokens`, 30-day retention).
|
||||
|
||||
## Auth Email (SMTP)
|
||||
|
||||
- Verify/reset use one-time opaque tokens (SHA-256 hash + pepper in DB, TTL 1 hour); raw token omitted from production email bodies
|
||||
- Env: `SMTP_HOST`, `SMTP_PORT`, `SMTP_FROM`, `FRONTEND_URL`, `EMAIL_DELIVERY_MODE`
|
||||
- Docker dev: `EMAIL_DELIVERY_MODE=memory` (no real SMTP required)
|
||||
- Local SMTP: Mailpit/Mailhog on `localhost:1025` (`SMTP_HOST=localhost`, `SMTP_PORT=1025`)
|
||||
- Tests: `EMAIL_DELIVERY_MODE=memory` (in-memory outbox)
|
||||
|
||||
## Profile & Avatar (MinIO)
|
||||
|
||||
- `GET/PATCH /api/v1/users/me`, `POST /api/v1/users/me/password`, `POST /api/v1/users/me/avatar`
|
||||
- Avatar: jpeg/png/webp, max 2 MB, re-encoded via Pillow (SVG rejected)
|
||||
- Storage env: `STORAGE_MODE` (`s3` or `memory`), `S3_ENDPOINT`, `S3_ACCESS_KEY`, `S3_SECRET_KEY`, `S3_BUCKET`
|
||||
- Local dev: MinIO on `localhost:9000` (console `9001`), bucket `compton` (created on API startup)
|
||||
- Tests: `STORAGE_MODE=memory` (in-memory file store, no MinIO)
|
||||
|
||||
## Admin Panel
|
||||
|
||||
- Route: `/admin` (`AdminGuard`, role `admin`)
|
||||
- UI: WESP-style **Ant Design** shell with fixed left sidebar, content topbar, and a dedicated admin theme system
|
||||
- Topbar actions:
|
||||
- **Light/Dark** — toggles isolated admin theme (preference persisted in `localStorage`)
|
||||
- **«На сайт»** — opens the static landing at `/` (`index.html`) via full navigation
|
||||
- **Logout** — revokes refresh cookie and redirects to `/login`
|
||||
- Reload UX: no blocking «Loading…» screens; admin routes preload the current admin background (`#f6f6f4` in Light, `#1f2229` in Dark) to avoid flash
|
||||
- Sections:
|
||||
- **Users** — list/create/patch role, status, password
|
||||
- **Content** — CMS CRUD
|
||||
- **Security** *(superuser only)* — runtime toggles + Install Secrets reveal
|
||||
- **Diagnostics** *(superuser only)* — security health checks (JWT, CORS, DB credentials, SSL mode, token table size, install secrets lock)
|
||||
- **Activity** — admin audit feed + server log tail
|
||||
- Business rules: last admin protected, no self-demotion, no self-block; critical endpoints require `is_superuser`
|
||||
|
||||
### Auth UI (zootech)
|
||||
|
||||
Auth pages (`/login`, `/register`, `/forgot-password`, `/reset-password`, `/verify`) use a WESP-inspired zootech card layout with organic theme tokens (`#48816d`, centered card, icon inputs). `AppHeader` is hidden on auth routes and in `/admin`.
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
# Frontend unit/component (coverage ≥ 85%)
|
||||
pnpm --filter web test:ci
|
||||
|
||||
# Same, inside Docker web container (when pnpm is not installed on host)
|
||||
docker compose --profile docker-web exec web sh -c "cd apps/web && pnpm test:ci"
|
||||
|
||||
# Backend unit/integration (coverage ≥ 90%)
|
||||
cd apps/api && python -m pytest --cov=app --cov-fail-under=90
|
||||
|
||||
# E2E regression (Playwright §15.7, 16 critical scenarios)
|
||||
pnpm --filter web e2e
|
||||
```
|
||||
|
||||
### E2E (Playwright)
|
||||
|
||||
- `pnpm --filter web e2e` starts a dedicated API (`:8001`) and Vite (`:5175`) — does not conflict with dev on `:5173` or Docker API on `:8000`
|
||||
- Uses SQLite + `EMAIL_DELIVERY_MODE=memory` + `ENABLE_TEST_ROUTES=true`
|
||||
- Test email tokens: `GET /api/v1/test/emails/latest-token` (only when test routes enabled)
|
||||
- Reuse running API: `E2E_START_API=false E2E_API_URL=http://localhost:8000 pnpm --filter web e2e`
|
||||
- Install browsers once: `pnpm --filter web exec playwright install chromium`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Solution |
|
||||
| ------- | -------- |
|
||||
| **`docker compose up` fails: install.env missing** | Run `python apps/api/scripts/bootstrap_install.py` before first start |
|
||||
| **API exits: `password authentication failed for user "compton_app"`** | Postgres volume was initialized before bootstrap. Reset local dev data: `docker compose --profile docker-web down -v`, bootstrap again, then `docker compose --profile docker-web up -d --build`. See [docs/secrets-recovery.md](docs/secrets-recovery.md) |
|
||||
| **Login failed** with correct credentials | Check API: `curl http://localhost:8000/api/v1/health`. Ensure `apps/web/.env` exists for local frontend. Restart: `docker compose restart web` or `pnpm --filter web dev` |
|
||||
| **401 on `/auth/refresh` in browser console (guest)** | Expected for logged-out users on public pages — refresh is skipped unless a session hint or protected path (`/admin`, `/profile`) triggers bootstrap |
|
||||
| **Logged out after F5 on `/admin`** | Usually a failed refresh (expired cookie) or stale Docker web build. Rebuild: `docker compose --profile docker-web up -d --build web`. Log in again if the refresh cookie expired |
|
||||
| **«На сайт» in admin does nothing / goes to login** | Must use full navigation to `/` (static landing), not React Router. Ensure topbar action is `href="/"`, then rebuild web container if behavior persists |
|
||||
| **White flash on admin reload** | `app.html` preloads admin background before React mount. Verify `localStorage.wespAdminTheme` and rebuild web if stale assets are served |
|
||||
| **Sidebar should stay visible while scrolling** | Admin sidebar is fixed on desktop (`position: fixed`, `height: 100vh`) and switches back to normal flow on mobile (`<=768px`) |
|
||||
| **Port 5173 already in use** | Stop Docker web: `docker compose --profile docker-web stop web`. Or stop local Vite |
|
||||
| **Frontend in Docker: missing modules / esbuild errors** | Rebuild: `docker compose --profile docker-web up -d --build web`. Do not run `pnpm install` on the host for the Docker workflow |
|
||||
| **Hot-reload not working in Docker (Windows/macOS)** | Enabled via `CHOKIDAR_USEPOLLING=true`. Restart: `docker compose --profile docker-web restart web` |
|
||||
| **Admin panel missing Security/Diagnostics tabs** | Log in as `admin@compton.example` (superuser), not `ops@compton.example` |
|
||||
| **Need DB password for DBeaver** | Admin → Security → Install Secrets → Reveal DB password (superuser), then `docker-compose.dev-ports.yml` |
|
||||
| **Lost `install.env` / DB auth failed** | See [docs/secrets-recovery.md](docs/secrets-recovery.md). Do not regenerate secrets if Postgres volume already exists |
|
||||
| **CORS errors in browser console** | Keep `VITE_USE_API_PROXY=true` (default in dev). Do not call `localhost:8000` directly from the browser |
|
||||
|
||||
## Security Highlights
|
||||
|
||||
All cryptographic primitives live in a single module (`apps/api/app/core/crypto.py`); `security.py` and `media_signing.py` re-export from it.
|
||||
|
||||
| Layer | Mechanism |
|
||||
| ----- | --------- |
|
||||
| **Passwords** | bcrypt cost 12 with per-user salt (embedded in hash); shared denylist |
|
||||
| **Access JWT** | HS256, short TTL, in-memory on frontend only |
|
||||
| **Refresh tokens** | HttpOnly cookie (`Path=/api/v1/auth`), rotation + family reuse detection; SHA-256 hash with server-side **pepper** |
|
||||
| **Email/reset tokens** | Opaque tokens, SHA-256 + pepper in DB; raw token never stored |
|
||||
| **Media URLs** | HMAC-SHA256 signed paths with TTL |
|
||||
| **Install secrets** | `secrets.token_*` generation; generate-once + lock; superuser reveal (audited) |
|
||||
|
||||
**Auth & API**
|
||||
|
||||
- Refresh token rotation and reuse-detection (family revoke)
|
||||
- Rate limiting and brute-force lockout (`ENABLE_RATE_LIMIT=true` in Docker compose)
|
||||
- Pending/blocked users rejected on refresh; frontend clears session on `403` (`ACCOUNT_BLOCKED`, `EMAIL_NOT_VERIFIED`)
|
||||
- Origin/Referer validation on cookie-based auth endpoints
|
||||
- `TRUSTED_PROXY_IPS` — only listed proxies may influence client IP via `X-Forwarded-For`
|
||||
- Email bodies omit raw tokens in production (`EMAIL_DELIVERY_MODE=memory` or `ENABLE_TEST_ROUTES=true` only)
|
||||
|
||||
**Data & infra**
|
||||
|
||||
- PostgreSQL/Redis/MinIO on internal Docker network by default (no host ports)
|
||||
- DB CHECK constraints, connection pool tuning, startup token cleanup
|
||||
- CMS HTML sanitization (bleach) with allowed URL protocols: `http`, `https`, `mailto`
|
||||
- Signed avatar URLs; RBAC + superuser guards on admin routes
|
||||
- Admin password policy on user create/reset
|
||||
|
||||
**Production guards** (`APP_ENV=production` — API refuses to start if):
|
||||
|
||||
- `ENABLE_TEST_ROUTES=true`
|
||||
- `ENABLE_DOCS=true`
|
||||
- `ENABLE_RATE_LIMIT=false`
|
||||
- `COOKIE_SECURE=false`
|
||||
- Default DB credentials (`user:pass`) or placeholder JWT secrets
|
||||
- `DATABASE_URL` without `sslmode=require`
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [docs/secrets-recovery.md](docs/secrets-recovery.md) — recover from lost install secrets
|
||||
- [docs/security-checklist.md](docs/security-checklist.md) — pre-production security checklist
|
||||
- [docs/TZ.md](docs/TZ.md) — full technical specification
|
||||
|
||||
## MVP Status (Phase 1)
|
||||
|
||||
| # | Feature | Status |
|
||||
| --- | ----------------------------- | ------ |
|
||||
| 1.1 | PostgreSQL + Alembic + seed | done |
|
||||
| 1.2 | Auth + SMTP (verify/reset) | done |
|
||||
| 1.3 | Profile CRUD + avatar (MinIO) | done |
|
||||
| 1.4 | Content pages | done |
|
||||
| 1.5 | Admin panel (Ant Design + zootech auth UI) | done |
|
||||
| 1.6 | E2E regression §15.7 | done |
|
||||
Reference in New Issue
Block a user