Шхуна не тонет: security, infra и доки на русском.
Безопасность довёл до ума — Cursor-генерацию переписал руками. IDOR закрыл, CSRF задушил, refresh rotation теперь как надо. HSTS на staging, ENABLE_DOCS=false, install.env recovery протестил. Backend: - jwt_denylist + auth_epoch: мгновенный revoke access JWT (logout/block/reset) - auth/admin/users: bump epoch, logout с Bearer, forgot_password skip для blocked - install_secrets: путь всегда apps/api/data/secrets/ (bootstrap из корня не ломает Docker) - seed: SEED_DEMO_USERS=false на prod/staging - тесты: jwt revoke, integration, coverage gate 90% Frontend: - logout шлёт Bearer, обработка TOKEN_REVOKED - guards TypeScript fix - E2E: blocked user → 401 сразу после block Infra: - staging/prod compose, TLS nginx, deploy-скрипты - k6 §17.2, backup/health/smoke scripts Docs: - docs/ на русском: project, security, deploy, release (старые md слили) - README короткий + план ТЗ + стандартные логины dev Код готов к плаванию. Капитан может идти писать фронт.
This commit is contained in:
@@ -1,426 +1,112 @@
|
||||
# Compton Platform
|
||||
|
||||
Monorepo-lite project that follows the `docs/TZ.md` specification for the Compton platform.
|
||||
Если вы открыли этот файл — поздравляем: перед вами monorepo с React, FastAPI и амбициями выйти в production. Амбиции живут в [docs/release.md](docs/release.md), код — в `apps/`, секреты — **не** в git.
|
||||
|
||||
## Stack
|
||||
Платформа Compton Organic Tech: лендинг, auth, профиль, CMS, админка в стиле WESP/zootech. ТЗ: [docs/TZ.md](docs/TZ.md).
|
||||
|
||||
- **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
|
||||
python apps/api/scripts/bootstrap_install.py # один раз, до первого up
|
||||
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
|
||||
curl http://localhost:8000/api/v1/health # Windows: curl.exe
|
||||
```
|
||||
|
||||
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.
|
||||
| URL | Что там |
|
||||
|-----|---------|
|
||||
| http://localhost:5173 | Лендинг `/` + SPA (`/login`, `/admin`, …) |
|
||||
| http://localhost:8000/api/v1/health | API жив? |
|
||||
| http://localhost:8000/api/v1/docs | OpenAPI (только dev) |
|
||||
|
||||
The `web` container runs Vite with hot-reload; dependencies are installed inside the container automatically.
|
||||
**Логин для пробы:** `admin@compton.example` / `Admin1234` → `/admin`.
|
||||
|
||||
Log in as `admin@compton.example` (password `Admin1234` by default) and open `/admin`. See [Seed data](#seed-data) for all demo accounts.
|
||||
> Bootstrap **до** первого `docker compose up`. Иначе Postgres скажет «password authentication failed» — см. [docs/deploy.md § восстановление](docs/deploy.md#восстановление-секретов).
|
||||
|
||||
**Optional** — copy env files if you also run API or frontend locally (hybrid mode):
|
||||
**Не делайте:** Docker `web` и `pnpm --filter web dev` одновременно — оба хотят порт **5173**.
|
||||
|
||||
## Демо-аккаунты (dev)
|
||||
|
||||
Подробнее: [docs/deploy.md § стандартные логины](docs/deploy.md#стандартные-логины-dev).
|
||||
|
||||
| Email | Пароль | Env | Роль | Superuser |
|
||||
|-------|--------|-----|------|-----------|
|
||||
| admin@compton.example | Admin1234 | `ADMIN_INITIAL_PASSWORD` | admin | да — Security, Diagnostics, secrets |
|
||||
| ops@compton.example | OpsAdmin1234 | `DEMO_OPS_PASSWORD` | admin | нет — Users, Content, Activity |
|
||||
| user@compton.example | User1234 | `DEMO_USER_PASSWORD` | user | — |
|
||||
|
||||
CMS после seed: `/pages/about`, `/pages/privacy`, `/pages/terms`.
|
||||
|
||||
В production/staging demo-users **выключены** (`SEED_DEMO_USERS=false`).
|
||||
|
||||
## Тесты
|
||||
|
||||
```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%)
|
||||
pnpm --filter web test:ci # frontend, cov ≥ 85%
|
||||
cd apps/api && python -m pytest --cov=app --cov-fail-under=90
|
||||
|
||||
# E2E regression (Playwright §15.7, 16 critical scenarios)
|
||||
pnpm --filter web e2e
|
||||
pnpm --filter web e2e # Playwright, API :8001
|
||||
```
|
||||
|
||||
### 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`
|
||||
Вся нормальная дока — в **`docs/`**, на русском, без водянистого README на 500 строк:
|
||||
|
||||
## Troubleshooting
|
||||
| Файл | О чём |
|
||||
|------|-------|
|
||||
| [docs/README.md](docs/README.md) | Оглавление |
|
||||
| [docs/project.md](docs/project.md) | Структура, архитектура, API, маршруты |
|
||||
| [docs/security.md](docs/security.md) | Auth, JWT revoke, секреты, чеклист prod |
|
||||
| [docs/deploy.md](docs/deploy.md) | Dev/staging/prod, бэкапы, troubleshooting |
|
||||
| [docs/release.md](docs/release.md) | QA gates, k6, ZAP, go-live |
|
||||
| [docs/TZ.md](docs/TZ.md) | Техническое задание |
|
||||
|
||||
| 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 |
|
||||
## Работа с Git
|
||||
|
||||
## Security Highlights
|
||||
**Репозиторий:** https://git.groupkomton.ru/Matvey/site.git
|
||||
**Ветка:** `main` (одна ветка, без сюрпризов)
|
||||
|
||||
All cryptographic primitives live in a single module (`apps/api/app/core/crypto.py`); `security.py` and `media_signing.py` re-export from it.
|
||||
```bash
|
||||
git pull origin main # перед работой — всегда
|
||||
git status && git diff # перед коммитом — тоже всегда
|
||||
git add .
|
||||
git commit -m "Сделал то-то на русском"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
| 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) |
|
||||
**Коммиты — только на русском.** Плохо: `fix`, `WIP`, `asdf`.
|
||||
|
||||
**Auth & API**
|
||||
**Не коммитить:** `.env`, `install.env`, `node_modules/`, логи, кэши.
|
||||
|
||||
- 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)
|
||||
**Не делать** `git push --force` на `main` без согласования.
|
||||
|
||||
**Data & infra**
|
||||
| Команда | Зачем |
|
||||
|---------|-------|
|
||||
| `git log --oneline -10` | что было |
|
||||
| `git checkout -- файл` | откатить файл |
|
||||
| `git stash` / `git stash pop` | спрятать / вернуть правки |
|
||||
|
||||
- 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
|
||||
## План по ТЗ (§16)
|
||||
|
||||
**Production guards** (`APP_ENV=production` — API refuses to start if):
|
||||
| Фаза | Задачи | Статус |
|
||||
|------|--------|--------|
|
||||
| **0 — Подготовка** | Monorepo, Docker Compose, UI kit, CI/coverage gates, Playwright smoke | Выполнено |
|
||||
| **1 — MVP** | Landing, Auth+SMTP, Profile, Content, Admin, staging + полный регресс | В процессе |
|
||||
| **2 — v1.0** | Celery/notifications, Catalog+Orders, Analytics, k6 + monitoring | Когда начнём? |
|
||||
| **3 — Scale** | Read replica PG, Celery workers, CDN, horizontal API | Когда начнём? |
|
||||
|
||||
- `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`
|
||||
### Фаза 1 — детализация
|
||||
|
||||
## Related Documentation
|
||||
| # | Задача | Модули | Статус |
|
||||
|---|--------|--------|--------|
|
||||
| 1.1 | Landing (перенос заглушки) | landing | Выполнено |
|
||||
| 1.2 | Auth + SMTP | auth | Выполнено |
|
||||
| 1.2b | Security baseline | core, auth | В процессе |
|
||||
| 1.3 | Profile CRUD + avatar | profile | Выполнено |
|
||||
| 1.4 | Content pages | content | Выполнено |
|
||||
| 1.5 | Admin panel | admin | Выполнено |
|
||||
| 1.6 | Staging deploy + полный регресс (§15.7) | — | В процессе |
|
||||
|
||||
- [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 — §17.1 ТЗ (k6 50 CCU, Lighthouse ≥85, E2E 10/10, coverage gates).
|
||||
|
||||
## 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 |
|
||||
*Дальше — [docs/](docs/README.md). Там схемы, таблицы и вещи, которые не помещаются в README без страданий.*
|
||||
|
||||
Reference in New Issue
Block a user