Commit Graph

4 Commits

Author SHA1 Message Date
MechaCat02
8faf702208 feat(media): authenticated signed media gateway (C1, C2-sink, C3, H2, H10)
Replaces the DB-blind `/media` ServeDir with a signed, DB-aware gateway at
`GET /media/{kind}/{id}`. Every media byte now flows through an HMAC-SHA256
signature check (minted into feed/upload DTOs for authenticated members; <img>
can't carry a Bearer header) plus a DB lookup:

- C1: export ZIP/HTML have no upload row, so they are unreachable by path —
  download stays behind the authenticated /export endpoints.
- C2 (sink): responses carry X-Content-Type-Options: nosniff and a locked-down
  CSP (default-src 'none'; sandbox), neutralizing any active content.
- C3 / H2: find_by_id filters deleted_at and the handler rejects ban-hidden
  uploaders, so deleted and moderated artifacts 404 — and the unauthenticated
  get_original alias (the H2 hole) is removed entirely.
- H10: delete paths (owner + host) now unlink original/preview/thumbnail after
  commit; soft_delete returns the paths; an hourly reaper reclaims disk for
  rows soft-deleted past a 1-day grace and hard-deletes them (FKs cascade).

Signed URLs are bucketed to a 1h window so they stay stable across feed polls
(browser cache hits) while expiring within 24h. media_token sign/verify has a
unit test (roundtrip + tamper + expiry).

Frontend: FeedUpload/pickMediaUrl now use the backend-provided signed
original_url; no client constructs a media path anymore.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:30:30 +02:00
MechaCat02
2068e8c1f3 fix(auth): reconcile role/ban from DB, revoke sessions, fix pin/comment bugs
H1: AuthUser now JOINs session→user and sources role/ban/event from the live
DB row instead of trusting JWT claims. Bans take effect on the next request;
demotion/promotion is immediate. Adds Session::find_auth_context and rejects
tokens whose sub/event_id disagree with the session row. This also closes M2
(banned export) and M3 (banned edit/delete) globally — banned users can no
longer pass the AuthUser extractor.

Adds Session::delete_by_user_id and revokes all of a user's sessions on
ban_user, set_role, and reset_user_pin so existing JWTs die immediately.
ban_user also emits a user-banned SSE for forced client logout.

H9: reset_user_pin used a non-existent column pin_failed_attempts (runtime
sqlx, so it 500'd in prod). Corrected to failed_pin_attempts — the only
account-recovery path now works.

C4: LightboxModal posted comments to /comment (singular); backend only
registers /comments. One-character route fix re-enables the comment feature.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:14:22 +02:00
MechaCat02
d228676a56 fix(security): cross-event authz, SSE ticket flow, account hardening, audit logs
Follow-up to the comprehensive code review. Five batches:

1. Cross-event authorization: host_delete_upload, unban_user, and
   host_delete_comment now scope by auth.event_id. Adds
   Upload::find_by_id_and_event / soft_delete_in_event and a
   Comment::soft_delete_in_event variant that joins through upload.

2. Token exposure: SSE auth no longer puts the JWT in the URL.
   New /api/v1/stream/ticket endpoint mints a short-lived single-use
   ticket bound to the session; the EventSource passes ?ticket=...
   instead. Refuse to start in APP_ENV=production with the dev JWT
   sentinel; warn loudly otherwise.

3. Account hardening: per-IP+name rate limit on /recover (mitigates
   targeted lockout DoS), per-IP rate limit on /admin/login, random
   32-char admin recovery PIN (replaces "0000"), structured tracing
   events for wrong PIN, lockout, failed admin login, ban/unban/role
   change/pin-reset/host-delete.

4. DoS / correctness: comment listing paginated (LIMIT 50 + ?before=
   cursor), hashtag extraction whitelisted to ASCII alnum+underscore
   (≤40 chars) with unit tests, display_name / caption / comment body
   length validated in chars rather than bytes.

5. Cleanup: session-touch failures now logged, DATABASE_MAX_CONNECTIONS
   env var (default 10).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:00:51 +02:00
fabi
8b9d916265 feat: implement authentication flow
Backend:
- AppConfig, AppError, AppState modules for shared infrastructure
- JWT creation/verification with HS256 (jsonwebtoken crate)
- Session management: SHA-256 token hashing, DB-backed sessions
- Auth middleware: AuthUser, RequireHost, RequireAdmin extractors
- POST /api/v1/join: name-only registration, 4-digit PIN + bcrypt hash
- POST /api/v1/recover: PIN-based recovery with 3-attempt lockout (15 min)
- POST /api/v1/admin/login: bcrypt password verification
- DELETE /api/v1/session: logout (session invalidation)
- Migration 006: user PIN lockout columns (failed_pin_attempts, pin_locked_until)
- Models: Event, User (with role enum), Session with all CRUD methods

Frontend:
- api.ts: typed fetch wrapper with automatic Bearer token injection
- auth.ts: JWT/PIN localStorage management with Svelte store
- /join: name entry form with PIN display modal and copy button
- /recover: name + PIN recovery form with saved PIN pre-fill
- /feed: placeholder gallery page with logout
- Root layout: auth initialization on mount
- Root page: redirect to /join or /feed based on auth state

All responses use German language strings as specified.

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