# Безопасность

Compton MVP — не банк, но и не «admin/admin в prod». Ниже — как устроена защита и что проверить перед выкладкой.

## Auth: схема

```mermaid
sequenceDiagram
  participant B as Браузер
  participant API as FastAPI
  participant RD as Redis
  participant PG as PostgreSQL

  B->>API: POST /auth/login
  API->>PG: bcrypt verify
  API->>RD: read auth_epoch
  API-->>B: access JWT (memory) + refresh cookie

  B->>API: GET /users/me + Bearer
  API->>RD: jti denied? epoch ok?
  alt revoked
    API-->>B: 401 TOKEN_REVOKED
  else ok
    API-->>B: 200
  end

  B->>API: POST /auth/logout
  API->>RD: deny_jti + revoke refresh
  API-->>B: cookie cleared
```

## Токены

| Токен | Где живёт | Отзыв |
|-------|-----------|-------|
| Access JWT | память frontend | jti denylist + auth_epoch (Redis) |
| Refresh | HttpOnly cookie | rotation + family reuse detection |
| Email/reset | opaque → hash в БД | one-time, TTL 1ч |

JWT claims: `sub`, `role`, `jti`, `auth_epoch`, `exp`.

### Мгновенный revoke

`apps/api/app/core/jwt_denylist.py`:

| Redis key | Смысл |
|-----------|-------|
| `jwt:deny:{jti}` | конкретный access-токен |
| `auth:epoch:{user_id}` | версия сессий пользователя |

| Событие | Действие |
|---------|----------|
| Logout | deny jti + revoke refresh |
| Block | INCR epoch + revoke refresh |
| Смена/reset пароля | INCR epoch + revoke refresh |

**Production:** без Redis API не стартует. Redis упал — fail-closed (401, не «ну ладно»).

**Dev:** in-memory fallback (не путать с prod).

## Криптография

Всё через `apps/api/app/core/crypto.py`:

| Данные | Метод |
|--------|-------|
| Пароли | bcrypt cost 12 |
| Access JWT | HS256 |
| Refresh/email tokens | SHA-256 + pepper |
| Media URLs | HMAC-SHA256 + TTL |
| Install secrets | `secrets.token_*`, generate-once + lock |

## Install secrets

```bash
python apps/api/scripts/bootstrap_install.py   # до первого docker compose up
```

| Файл | Содержимое |
|------|------------|
| `data/secrets/install.env` | PG, JWT, S3, MinIO |
| `install.meta.json` | install ID, lock time |

**Не ротировать** `POSTGRES_PASSWORD` / JWT после bootstrap без плана — иначе Postgres скажет фразу, которую вы уже видели, и будет прав.

Reveal: Admin → Security → Install Secrets (superuser, аудит в `admin-audit.jsonl`).

Восстановление: [deploy.md § восстановление](./deploy.md#восстановление-секретов).

## RBAC

| Правило | Enforcement |
|---------|-------------|
| `/admin` → role=admin | Guard + API |
| Security/Diagnostics/secrets → superuser | API + UI tabs |
| Нельзя block/demote себя | admin service |
| Last admin protected | admin service |
| blocked/pending → 403 refresh | auth service |
| forgot_password skip для blocked | auth service |
| IDOR на профиль | users router |

## HTTP / инфра

### Nginx headers

| Header | Значение |
|--------|----------|
| X-Frame-Options | DENY |
| X-Content-Type-Options | nosniff |
| Referrer-Policy | strict-origin-when-cross-origin |
| HSTS | `default.tls.conf` (staging/prod) |

### Прочее

- Origin/Referer на cookie-auth endpoints
- Rate limit (prod: обязателен)
- CMS: bleach, протоколы http/https/mailto
- Avatar: jpeg/png/webp, re-encode, **SVG — нет**
- CI: Bandit, pip-audit, gitleaks, npm audit
- Postgres/Redis/MinIO — internal network
- Firewall VPS: 22, 80, 443

## Production guards

`APP_ENV=production` → API **не стартует**, если:

| Проблема | Env |
|----------|-----|
| Test routes | `ENABLE_TEST_ROUTES=true` |
| OpenAPI | `ENABLE_DOCS=true` |
| Rate limit off | `ENABLE_RATE_LIMIT=false` |
| Cookie без Secure | `COOKIE_SECURE=false` |
| Placeholder JWT | `change-me-*` |
| Дефолтная БД | user:pass |
| Без SSL mode | нет `sslmode=require` |
| Без Redis | JWT revocation |

## Prod env (минимум)

| Переменная | Значение |
|------------|----------|
| `APP_ENV` | production |
| `ENABLE_DOCS` | false |
| `ENABLE_TEST_ROUTES` | false |
| `COOKIE_SECURE` | true |
| `ENABLE_RATE_LIMIT` | true |
| `EMAIL_DELIVERY_MODE` | smtp |
| `SEED_DEMO_USERS` | false |
| `DATABASE_URL` | …?sslmode=require |

## Чеклист

### Сделано в коде

- [x] JWT в memory, refresh HttpOnly
- [x] Anti-enumeration auth
- [x] Origin/Referer validation
- [x] RBAC + superuser
- [x] CMS sanitization
- [x] Nginx security headers
- [x] CI security scans
- [x] Install secrets bootstrap + lock
- [x] JWT jti denylist + auth_epoch
- [x] Admin audit log

### Проверить на staging

- [ ] `/api/v1/docs` → 404
- [ ] HSTS за TLS
- [ ] Dry-run recovery секретов
- [ ] ZAP: 0 High/Critical → [release.md](./release.md)
- [ ] k6 pass → [release.md](./release.md)
- [ ] Lighthouse ≥ 85 на `/`
