# Legal Case Tracker (FastAPI)
Backend skeleton: public requests + OTP + public JWT cookie + admin (admin/lawyer) + files (self-hosted S3) + SLA/auto-assign (Celery) + quotes + dedicated chat microservice.

## Run (Docker)
```bash
cp .env.example .env
docker compose -f docker-compose.yml -f docker-compose.local.yml up --build
```
Landing (frontend): http://localhost:8081
Admin UI: http://localhost:8081/admin
API (backend): http://localhost:8002
Swagger: http://localhost:8002/docs
Chat service health (via nginx): http://localhost:8081/chat-health
Email service health (via nginx): http://localhost:8081/email-health

## Testing (containers only)
Rule: backend tests must be executed only inside Docker containers. Do not run tests from host Python environment.

Start local stack (if not started):
```bash
docker compose -f docker-compose.yml -f docker-compose.local.yml up -d
```

Run full backend suite via `unittest` (default project mode):
```bash
docker compose -f docker-compose.yml -f docker-compose.local.yml exec -T backend \
  python -m unittest discover -s tests -p "test_*.py" -v
```

Run target tests via `pytest` inside one-off backend container:
```bash
docker compose -f docker-compose.yml -f docker-compose.local.yml run --rm --no-deps backend \
  sh -lc 'pip install --no-cache-dir pytest >/tmp/pip_pytest.log && python -m pytest -q tests/test_public_requests.py tests/test_public_cabinet.py'
```
Use this command when targeted `pytest` checks are required and `pytest` is not preinstalled in the backend image.

## Production (ruakb.ru + ruakb.online, 80/443, TLS via Nginx + Certbot)
Production stack uses dedicated edge nginx (`docker-compose.prod.nginx.yml`).

Use production template before first deploy:
```bash
cp .env.production .env
```

Prerequisites:
- DNS `A` record: `ruakb.ru -> 45.150.36.116`
- Optional DNS `A` record: `www.ruakb.ru -> 45.150.36.116`
- DNS `A` record: `ruakb.online -> 45.150.36.116`
- Optional DNS `A` record: `www.ruakb.online -> 45.150.36.116`
- Open server ports: `80/tcp`, `443/tcp`
- DB credentials in `.env` must be consistent:
  - `DATABASE_URL=postgresql+psycopg://postgres:<password>@db:5432/legal`
  - `POSTGRES_PASSWORD=<same password>`

Production security baseline in `.env`:
```bash
APP_ENV=prod
PRODUCTION_ENFORCE_SECURE_SETTINGS=true
OTP_DEV_MODE=false
ADMIN_BOOTSTRAP_ENABLED=false
PUBLIC_COOKIE_SECURE=true
PUBLIC_COOKIE_SAMESITE=lax
PUBLIC_ALLOWED_WEB_ORIGINS=https://ruakb.ru,https://www.ruakb.ru,https://ruakb.online,https://www.ruakb.online
CORS_ORIGINS=https://ruakb.ru,https://www.ruakb.ru,https://ruakb.online,https://www.ruakb.online
CORS_ALLOW_METHODS=GET,POST,PUT,PATCH,DELETE,OPTIONS
CORS_ALLOW_HEADERS=Authorization,Content-Type,X-Requested-With,X-Request-ID
S3_USE_SSL=true
S3_VERIFY_SSL=true
S3_CA_CERT_PATH=/etc/ssl/minio/ca.crt
MINIO_TLS_ENABLED=true
CHAT_ENCRYPTION_SECRET=<strong-random-secret>
DATA_ENCRYPTION_SECRET=<strong-random-secret>
DATA_ENCRYPTION_ACTIVE_KID=<kid>
DATA_ENCRYPTION_KEYS=<kid>=<strong-random-secret>
CHAT_ENCRYPTION_ACTIVE_KID=<kid>
CHAT_ENCRYPTION_KEYS=<kid>=<strong-random-secret>
ADMIN_JWT_SECRET=<strong-random-secret>
PUBLIC_JWT_SECRET=<strong-random-secret>
INTERNAL_SERVICE_TOKEN=<strong-random-secret>
MINIO_ROOT_USER=<non-default-user>
MINIO_ROOT_PASSWORD=<strong-random-password>
```

Initialize internal TLS for MinIO before first production start:
```bash
make prod-minio-tls-init
```
This generates:
- `deploy/tls/minio/public.crt`
- `deploy/tls/minio/private.key`
- `deploy/tls/minio/ca.crt`

Production frontend/backend/worker trust `deploy/tls/minio/ca.crt` and use HTTPS to MinIO inside docker network.

Initial certificate issue (bootstrap with nginx on port 80 only):
```bash
make prod-cert-init LETSENCRYPT_EMAIL=you@example.com DOMAIN=ruakb.ru WWW_DOMAIN=www.ruakb.ru
```
By default `prod-cert-init` also includes `ruakb.online` and `www.ruakb.online`.
If needed, override:
```bash
make prod-cert-init \
  LETSENCRYPT_EMAIL=you@example.com \
  DOMAIN=ruakb.ru WWW_DOMAIN=www.ruakb.ru \
  SECOND_DOMAIN=ruakb.online SECOND_WWW_DOMAIN=www.ruakb.online
```

Regular production start/update:
```bash
make prod-up
```

`make prod-up` includes strict preflight checks (`scripts/ops/deploy_prod.sh`) and fails on insecure production env values
(weak/default secrets, localhost origins, disabled strict origin checks, non-TOTP-required admin auth, etc.).

Certificate renew:
```bash
make prod-cert-renew
```

Internal secret rotation (excluding external providers such as SMS/Telegram):
```bash
make prod-secrets-generate
```
This creates `.env.prod` with new generated internal secrets.

Apply generated secrets to running production stack:
```bash
make prod-secrets-apply
```
This will backup current `.env`, replace it with `.env.prod`, rotate Postgres password in DB,
recreate containers, run migrations, and execute health checks.

Encryption KID rotation (without data loss):
```bash
make rotate-encryption-kid
# restart backend/chat/worker
make reencrypt-active-kid
```
`rotate-encryption-kid` appends a new `kid=secret` into `DATA_ENCRYPTION_KEYS` and `CHAT_ENCRYPTION_KEYS`
and switches `*_ACTIVE_KID` to the new value.
`reencrypt-active-kid` re-encrypts historical invoice requisites, admin TOTP secrets, and chat message bodies.

Safety guard for apply mode:
- script requires confirmation token `ROTATE-PROD-SECRETS`;
- default `make prod-secrets-apply` passes it via `CONFIRM_TOKEN`;
- you can override explicitly:
```bash
make prod-secrets-apply CONFIRM_TOKEN=ROTATE-PROD-SECRETS
```

Checks:
```bash
curl -I https://ruakb.ru
curl -fsS https://ruakb.ru/health
curl -fsS https://ruakb.ru/chat-health
docker compose -f docker-compose.yml -f docker-compose.prod.nginx.yml exec -T backend sh -lc 'python - <<PY\nfrom app.services.s3_storage import get_s3_storage\nprint(get_s3_storage().client._endpoint.host)\nPY'
ss -lntp | egrep ':(80|443|5432|6379|8002|8081|9000|9001)\\b'
```

## Incident response (PDn)
Create a standard incident checklist report:
```bash
make incident-checklist
```
or with details:
```bash
./scripts/ops/incident_checklist.sh \
  --severity HIGH \
  --category UNAUTHORIZED_ACCESS \
  --summary "Suspicious read access to request cards" \
  --track-number TRK-XXXX
```
Generated markdown report is saved to `reports/incidents/incident-<timestamp>.md`.

## Migrations
```bash
docker compose exec backend alembic upgrade head
```

## Seed Quotes (Upsert)
```bash
make seed-quotes
```
Loads 50 justice-themed quotes into `quotes` with idempotent upsert by `(author, text)`.

## OTP SMS provider (SMS Aero)
OTP sending is implemented through a dedicated SMS service layer (`app/services/sms_service.py`).

Public auth mode can be selected via environment:
```bash
PUBLIC_AUTH_MODE=sms          # sms | email | sms_or_email | totp
EMAIL_SERVICE_ENABLED=true    # false -> email-service stays healthy but sending is disabled
EMAIL_PROVIDER=dummy          # dummy | smtp | service
EMAIL_SERVICE_URL=http://email-service:8010
INTERNAL_SERVICE_TOKEN=change_me_internal_service_token
OTP_EMAIL_FALLBACK_ENABLED=true
OTP_SMS_MIN_BALANCE=20
ADMIN_AUTH_MODE=password_totp_optional  # password | password_totp_optional | password_totp_required
TOTP_ISSUER=Правовой Трекер
```

Configure provider in `.env`:
```bash
SMS_PROVIDER=smsaero
SMSAERO_EMAIL=your_email@example.com
SMSAERO_API_KEY=your_api_key
OTP_SMS_TEMPLATE=Your verification code: {code}
OTP_DEV_MODE=false
OTP_AUTOTEST_FORCE_MOCK_SMS=true
```

For SMTP email OTP:
```bash
EMAIL_PROVIDER=smtp
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=mailer@example.com
SMTP_PASSWORD=your_password
SMTP_FROM=mailer@example.com
SMTP_USE_TLS=true
SMTP_USE_SSL=false
OTP_EMAIL_SUBJECT_TEMPLATE=Код подтверждения: {code}
OTP_EMAIL_TEMPLATE=Ваш код подтверждения: {code}
```

For dedicated email microservice (recommended in production):
```bash
EMAIL_SERVICE_ENABLED=true
EMAIL_PROVIDER=service
EMAIL_SERVICE_URL=http://email-service:8010
INTERNAL_SERVICE_TOKEN=<strong-random-token>
```

To keep infrastructure healthy but disable email sending temporarily:
```bash
EMAIL_SERVICE_ENABLED=false
```

Admin/Lawyer TOTP endpoints:
- `GET /api/admin/auth/totp/status`
- `POST /api/admin/auth/totp/setup`
- `POST /api/admin/auth/totp/enable`
- `POST /api/admin/auth/totp/backup/regenerate`
- `POST /api/admin/auth/totp/disable`

For local/dev mock mode:
```bash
SMS_PROVIDER=dummy
```
In this mode OTP code is printed to backend logs.

You can also force mock mode with real provider settings:
```bash
OTP_DEV_MODE=true
```
When enabled, real SMS sending is disabled and OTP code is printed to backend logs.

Additionally, to protect SMS budget during automated tests:
```bash
OTP_AUTOTEST_FORCE_MOCK_SMS=true
```
When this flag is enabled and runtime is detected as autotest (`pytest/unittest/APP_ENV=test|ci`),
`SMS_PROVIDER=smsaero` is automatically forced to mock mode for OTP sending.

Admin health-check endpoint (no SMS send):
`GET /api/admin/system/sms-provider-health`

## Secure Chat (encrypted at rest)
Chat logic is isolated in `app/services/chat_secure_service.py`.

- Message bodies are encrypted before storing in DB (`messages.body`) and transparently decrypted on read.
- Encryption key priority:
  1. `CHAT_ENCRYPTION_SECRET`
  2. `DATA_ENCRYPTION_SECRET`
  3. JWT secrets fallback (not recommended for production)

Recommended production config:
```bash
CHAT_ENCRYPTION_SECRET=<long-random-secret>
DATA_ENCRYPTION_SECRET=<long-random-secret>
```

Chat API runs in a dedicated container (`chat-service`) with separate FastAPI entrypoint:
`app/chat_main.py`

Nginx routes only chat API prefixes to the chat container:
- `/api/public/chat/*`
- `/api/admin/chat/*`

## Attachment antivirus and content checks
Attachment scanning is asynchronous (Celery queue `uploads`) and supports ClamAV + content policy checks.

Environment flags:
```bash
ATTACHMENT_SCAN_ENABLED=true
ATTACHMENT_SCAN_ENFORCE=true
ATTACHMENT_ALLOWED_MIME_TYPES=application/pdf,image/jpeg,image/png,video/mp4,text/plain
CLAMAV_ENABLED=true
CLAMAV_HOST=clamav
CLAMAV_PORT=3310
CLAMAV_TIMEOUT_SECONDS=20
```

Compose profiles by environment:
- local (`docker-compose.local.yml`): `clamav` uses `mkodockx/docker-clamav:alpine` (multi-arch, including arm64).
- prod (`docker-compose.prod*.yml`): `clamav` stays on official `clamav/clamav` with `platform: linux/amd64`.

Scan statuses on `attachments`:
- `PENDING` (file uploaded, scan in progress)
- `CLEAN` (safe to download)
- `INFECTED` (blocked)
- `ERROR` (scan failed, blocked when enforcement is on)

When `ATTACHMENT_SCAN_ENFORCE=true`, public/admin download endpoints block non-clean files.

## Security CI pipeline (SEC-14)
GitHub Actions workflow: `/Users/tronosfera/Develop/Law/.github/workflows/security-ci.yml`

Checks:
- SAST: `bandit` for `app/` and `scripts/`.
- Dependency scan: `pip-audit` for `requirements.txt`.
- Container scan: `trivy` on backend Docker image.

Fail thresholds (configurable in workflow `env`):
- `BANDIT_MAX_HIGH` (default `0`)
- `DEP_MAX_VULNS` (default `0`)
- `TRIVY_MAX_HIGH` (default `0`)
- `TRIVY_MAX_CRITICAL` (default `0`)

Artifacts uploaded for each run:
- `reports/security/bandit.json`
- `reports/security/pip-audit.json`
- `reports/security/sast-deps-summary.txt`
- `reports/security/trivy-image.json`
- `reports/security/trivy-image.sarif`
- `reports/security/trivy-summary.txt`

## Security smoke (SEC-15)
Local/manual run:
```bash
make security-smoke
```
or with explicit target URL:
```bash
./scripts/ops/security_smoke.sh https://ruakb.online
```

What is checked:
- public health endpoints (`/health`, `/chat-health`, `/email-health`);
- required security headers (`X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy`, `Content-Security-Policy`);
- TLS certificate availability/expiry (for `https://` URLs);
- production cookie/origin env flags (`PUBLIC_COOKIE_SECURE`, `PUBLIC_COOKIE_SAMESITE`, `PUBLIC_STRICT_ORIGIN_CHECK`);
- attachment scan service availability (`clamav`, when scan is enabled);
- DB access to `security_audit_log` (table exists + query works).

Report is saved to:
- `reports/security/security-smoke-<timestamp>.md`

Example cron (every 15 minutes):
```bash
*/15 * * * * cd /opt/Law && ./scripts/ops/security_smoke.sh https://ruakb.online >> /var/log/law-security-smoke.log 2>&1
```

## Full production security audit and auto-repair
Single command to verify full security block and auto-generate missing technical artifacts:
```bash
make prod-security-audit DOMAIN=ruakb.ru WWW_DOMAIN=www.ruakb.ru SECOND_DOMAIN=ruakb.online SECOND_WWW_DOMAIN=www.ruakb.online LETSENCRYPT_EMAIL=you@example.com
```

What this workflow does (`scripts/ops/prod_security_audit.sh`):
- checks required compose/nginx files;
- restores/generates `.env` if missing (`.env.prod` or `.env.production` + `rotate_prod_secrets.sh`);
- generates MinIO internal TLS bundle if missing (`prod-minio-tls-init` equivalent);
- starts/reconciles production stack (nginx profile);
- applies DB migrations;
- validates production security config (`validate_production_security_or_raise`);
- runs local and external security smoke checks;
- generates incident checklist snapshot report.

Optional: auto-bootstrap Let's Encrypt certs if HTTPS health is failing:
```bash
make prod-security-audit AUTO_CERT_INIT=1 DOMAIN=ruakb.ru WWW_DOMAIN=www.ruakb.ru SECOND_DOMAIN=ruakb.online SECOND_WWW_DOMAIN=www.ruakb.online LETSENCRYPT_EMAIL=you@example.com
```

If localhost loopback probes are not desired on production host:
```bash
make prod-security-audit SKIP_LOCAL_SMOKE=1 DOMAIN=ruakb.ru WWW_DOMAIN=www.ruakb.ru SECOND_DOMAIN=ruakb.online SECOND_WWW_DOMAIN=www.ruakb.online LETSENCRYPT_EMAIL=you@example.com
```

By default local smoke probes now try multiple loopback endpoints:
- `https://127.0.0.1`
- `https://localhost`
- `http://127.0.0.1`
- `http://localhost`

You can override:
```bash
make prod-security-audit LOCAL_SMOKE_CANDIDATES="https://127.0.0.1,http://127.0.0.1"
```

## Dedicated security scheduler entity
`security-scheduler` is a separate container service that runs periodic smoke checks automatically.

Start/update:
```bash
make prod-security-scheduler-up
```

Logs:
```bash
make prod-security-scheduler-logs
```

Main env knobs:
```bash
SECURITY_SCHEDULER_INTERVAL_SECONDS=900
SECURITY_SCHEDULER_INTERNAL_BASE_URL=http://frontend
SECURITY_SCHEDULER_EXTERNAL_DOMAINS=ruakb.ru,ruakb.online
SECURITY_SCHEDULER_SKIP_DOCKER_CHECKS=1
SECURITY_SCHEDULER_RUN_INCIDENT_ON_FAIL=1
```

Scheduler script:
- `/Users/tronosfera/Develop/Law/scripts/ops/security_scheduler.sh`

## Container health and alerting
Docker Compose is configured with:
- `restart: unless-stopped` for core services
- `healthcheck` for `db`, `redis`, `backend`, `chat-service`, `frontend`
- startup ordering via `depends_on: condition: service_healthy`

Quick checks:
```bash
docker compose up -d
docker compose ps
curl -fsS http://localhost:8081/health
curl -fsS http://localhost:8081/chat-health
curl -fsS http://localhost:8081/email-health
```

Alert-ready smoke script (for cron/CI):
```bash
./scripts/ops/check_chat_health.sh
```
Exit code `0` means healthy, non-zero means alert condition.
