Commit Graph

4 Commits

Author SHA1 Message Date
MechaCat02
2d7169e971 harden: low-severity bucket — unspoofable IP, admin floor, log/SSE hygiene
The cheap, real LOWs from the review (bucket 🅰); 🅱 items and won't-fix
decisions are recorded in docs/SECURITY-BACKLOG.md.

- XFF spoofing (highest-value): client_ip now prefers an unspoofable X-Real-IP
  (Caddy `header_up X-Real-IP {remote_host}`) and otherwise takes the *rightmost*
  X-Forwarded-For token, never the client-controlled leftmost. The join cap,
  recover throttle, and admin-login floor all keyed on this. Unit-tested.
  Caddyfile also drops the dead /media/previews|originals cache matchers (the
  gateway sets its own Cache-Control) and the false "only host can download"
  comment.
- admin_login: add a hard, non-disableable rate floor (30/5min/IP) so the DB
  rate-limit master toggle can't remove brute-force protection.
- SSE: the broadcast upload-error payload no longer includes the raw error
  (absolute filesystem paths) — generic message to clients, details logged
  server-side only.
- Access logs: the request span logs the path only, never the query string, so
  the replayable media `?sig=` capability isn't written to logs.
- Reaper TOCTOU: reap_deleted now deletes rows by the ids it actually unlinked
  (DELETE … WHERE id = ANY) instead of re-evaluating the time predicate, so a
  row crossing the grace line mid-sweep can't be row-deleted without its files
  unlinked.
- Dockerfile: run the backend as a non-root user; create /media owned by `app`
  so a fresh volume is writable.
- a11y: aria-labels on the upload caption and feed search inputs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 18:01:41 +02:00
MechaCat02
9364cb624a fix(dos): bound uploads, offload bcrypt, cap image decode, split queues
H3: re-enable a request-body limit (550MB, sized to the largest allowed video +
multipart overhead) instead of DefaultBodyLimit::disable(), and drop the
second full-file copy by keeping the upload as Bytes.

H4: image decode now goes through ImageReader with explicit dimension
(12000x12000) and allocation caps, rejecting decompression bombs before the
expensive resize, and the whole spawn_blocking is wrapped in a 120s timeout
mirroring the ffmpeg guard.

H8: every bcrypt hash/verify (join, recover, admin_login, host PIN reset) now
runs via spawn_blocking through a new services::password helper, so concurrent
auths can't pin the async workers.

M7: images and videos get independent compression permit pools, so slow videos
can no longer starve image-preview generation.

H5: add a generous per-IP daily account-creation cap (NAT-safe at ~100 guests)
on top of the burst cap, and wire up the previously-dead upload_count_quota_*
config as an event-wide file-count cap that a re-join cannot reset.

Also bumps generated recovery PINs from 4 to 6 digits (part of M4).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:38:59 +02:00
MechaCat02
141c918dd5 backend(infra): shared config helper, startup recovery, periodic maintenance
Foundations for the v0.16 features. No new endpoints here — those land in
the next commit on top of these.

- migrations 008 + 009: commit the load-bearing compression_status column
  that was uncommitted on disk; add 009_feature_toggles seeding the master
  + per-endpoint rate-limit switches, the master + per-area quota switches,
  and the admin-editable privacy_note.
- services/config.rs (new): get_str / get_i64 / get_usize / get_f64 / get_bool
  consolidating the scattered helpers that lived in three handlers.
- services/maintenance.rs (new):
  - startup_recovery() — resets compression_status='processing' and
    export_job.status='running' rows orphaned by a previous crashed
    instance, so users never see permanent "Wird vorbereitet…" spinners.
  - spawn_periodic_tasks() — hourly cleanup of expired sessions (rows
    were never pruned) + rate-limiter HashMap pruning (windows kept one
    entry per IP forever).
- services/jobs.rs (new sketch): BackgroundJob trait + JobContext for
  future jobs to plug into the same progress + SSE pipeline as
  compression/export. Not wired yet — codifies the convention.
- services/compression.rs: 120s hard timeout + kill_on_drop on ffmpeg
  so a malformed video can't hang and leak a worker semaphore permit.
- services/rate_limiter.rs: new prune() called from the periodic task.
- state.rs: SseEvent::new() constructor so event-type strings stay
  consistent instead of being typed inline at every emit site.
- models/user.rs: UserRole::as_str() for /me/context serialization.
- models/upload.rs: soft_delete() now runs in a transaction and
  decrements the uploader's total_upload_bytes (GREATEST(0, …) guard) —
  fixes a quota drift where deleting reclaimed no quota.
- Cargo.toml + Cargo.lock: add `infer = "0.15"` (multipart MIME sniffing
  used by the upload handler).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 14:31:41 +02:00
fabi
3f052a4f91 feat: implement upload pipeline with compression and SSE
Backend:
- POST /api/v1/upload: multipart file upload with caption + hashtags
  - Validates file size against DB config limits (image/video separate)
  - Checks user ban status and event upload lock
  - Saves original to disk under {media_path}/originals/{slug}/
  - Tracks user total_upload_bytes for quota enforcement
  - Extracts hashtags from caption text and explicit CSV field
  - Upserts hashtags and links them to uploads
- PATCH /api/v1/upload/{id}: edit caption and hashtags (owner only)
- DELETE /api/v1/upload/{id}: soft-delete (owner only)
- GET /api/v1/stream: SSE endpoint with 30s keepalive
  - Broadcasts new-upload events to all connected clients
  - Uses tokio broadcast channel for fan-out

Services:
- CompressionWorker: Tokio semaphore-bounded (concurrency=2) background processor
  - Images: resize to 800px wide JPEG preview via image crate
  - PNG originals: lossless compression via oxipng
  - Videos: ffmpeg thumbnail extraction (1 frame at 1s, scaled to 800px)
  - Updates upload record with preview_path/thumbnail_path on completion

Models:
- Upload with full CRUD (create, find, update caption, soft delete, set paths)
- Hashtag with upsert, link/unlink, extract_hashtags() text parser
- UploadDto for API serialization with like/comment counts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 21:48:59 +02:00