fix: echtes Session-Modell statt Query-Param-Token & Klartext-Secret #2

Merged
andre merged 2 commits from a2/session-model-hardening into main 2026-09-26 23:51:11 +02:00
Owner

Zusammenfassung

Löst die vier verbliebenen DeepSeek-R1-Funde aus PR #1 (siehe todo_partner-portal-kampagne-2027.md, Etappe 5, Abschnitt "Neu — aus DeepSeek-R1-Review von PR #1"), die bewusst nicht als Reflex-Fix behandelt wurden, sondern ein echtes Session-Design brauchten:

  1. Token im URL-Query-Param → HttpOnly-Cookie. Der Dashboard-Token lief bisher über /auth/verify?... Redirect-URL und /api/partner/dashboard?token= — leckt über Referrer-Header, Browser-Historie, Proxy-Logs. Jetzt: HttpOnly; Secure; SameSite=Lax-Cookie (ks_partner_session). Die Partner-Identität kommt ausschließlich aus dem Cookie, /api/partner/dashboard braucht kein ?id= mehr.
  2. Keine Ablauffrist → TTL. Magic-Link-Token: 7 Tage. Session: 30 Tage. Neue Tabellen magic_link_tokens und partner_sessions mit expires_at.
  3. Audit-Log-Mutation → dedizierte Tabelle. Die Single-Use-Markierung schrieb bisher in audit_log.details (verstößt gegen Append-only-Prinzip für Audit-Trails). Jetzt: magic_link_tokens.consumed_at, audit_log bleibt vollständig unangetastet.
  4. Klartext-Secret in der DB → gehasht. partners.dashboard_token (Klartext) ist komplett entfernt. Nur SHA-256-Hashes in magic_link_tokens.token_hash / partner_sessions.session_token_hash; Klartext existiert nur transient im Mail-Link bzw. Cookie.

Bonus: Re-Freigabe eines Partners (neue Tier-Stufe durch André) widerruft jetzt automatisch alle bestehenden Sessions (revokeAllSessions) — ein offenes altes Browser-Tab läuft nicht mit veralteten Rabattdaten weiter.

Refactor: Token-Verifikation aus api/admin/approve.js in eigenes Modul src/core/auth/session.js ausgelagert (Separation of Concerns, von DeepSeek-R1 als Wartbarkeits-Punkt genannt).

Migration

Kein Migrationsschritt nötig — die produktive data/partner.db auf maci ist noch leer (frisch deployter Prozess, PR #1 wurde erst vor Kurzem gemergt), keine echten Partner-Datensätze betroffen.

Test plan

  • npm test — 31/31 grün (2 neue Tests: Re-Approve widerruft alte Session, Session-Cookie-Attribute HttpOnly/Secure im Freigabe-Flow geprüft)
  • Nach Merge: git pull --ff-only && pm2 reload kurven-schule-partner auf maci, Smoke-Test curl -c - https://partner.kurvenschule.cloud/health

🤖 Generated with Claude Code

## Zusammenfassung Löst die vier verbliebenen DeepSeek-R1-Funde aus PR #1 (siehe `todo_partner-portal-kampagne-2027.md`, Etappe 5, Abschnitt "Neu — aus DeepSeek-R1-Review von PR #1"), die bewusst nicht als Reflex-Fix behandelt wurden, sondern ein echtes Session-Design brauchten: 1. **Token im URL-Query-Param → HttpOnly-Cookie.** Der Dashboard-Token lief bisher über `/auth/verify?...` Redirect-URL und `/api/partner/dashboard?token=` — leckt über Referrer-Header, Browser-Historie, Proxy-Logs. Jetzt: `HttpOnly; Secure; SameSite=Lax`-Cookie (`ks_partner_session`). Die Partner-Identität kommt ausschließlich aus dem Cookie, `/api/partner/dashboard` braucht kein `?id=` mehr. 2. **Keine Ablauffrist → TTL.** Magic-Link-Token: 7 Tage. Session: 30 Tage. Neue Tabellen `magic_link_tokens` und `partner_sessions` mit `expires_at`. 3. **Audit-Log-Mutation → dedizierte Tabelle.** Die Single-Use-Markierung schrieb bisher in `audit_log.details` (verstößt gegen Append-only-Prinzip für Audit-Trails). Jetzt: `magic_link_tokens.consumed_at`, `audit_log` bleibt vollständig unangetastet. 4. **Klartext-Secret in der DB → gehasht.** `partners.dashboard_token` (Klartext) ist komplett entfernt. Nur SHA-256-Hashes in `magic_link_tokens.token_hash` / `partner_sessions.session_token_hash`; Klartext existiert nur transient im Mail-Link bzw. Cookie. **Bonus:** Re-Freigabe eines Partners (neue Tier-Stufe durch André) widerruft jetzt automatisch alle bestehenden Sessions (`revokeAllSessions`) — ein offenes altes Browser-Tab läuft nicht mit veralteten Rabattdaten weiter. **Refactor:** Token-Verifikation aus `api/admin/approve.js` in eigenes Modul `src/core/auth/session.js` ausgelagert (Separation of Concerns, von DeepSeek-R1 als Wartbarkeits-Punkt genannt). ## Migration Kein Migrationsschritt nötig — die produktive `data/partner.db` auf maci ist noch leer (frisch deployter Prozess, PR #1 wurde erst vor Kurzem gemergt), keine echten Partner-Datensätze betroffen. ## Test plan - [x] `npm test` — 31/31 grün (2 neue Tests: Re-Approve widerruft alte Session, Session-Cookie-Attribute HttpOnly/Secure im Freigabe-Flow geprüft) - [ ] Nach Merge: `git pull --ff-only && pm2 reload kurven-schule-partner` auf maci, Smoke-Test `curl -c - https://partner.kurvenschule.cloud/health` 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix: echtes Session-Modell statt Query-Param-Token & Klartext-Secret
All checks were successful
Unit & Contract Tests / Node.js Contract Tests (pull_request) Successful in 7s
3680db8234
Löst die vier verbliebenen DeepSeek-R1-Funde aus PR #1, die bewusst
nicht als Reflex-Fix behandelt wurden, sondern ein Session-Design
brauchten:

- Dashboard-Token wanderte als Query-Param durch /auth/verify-Redirect
  und /api/partner/dashboard?token= — leckte über Referrer-Header,
  Browser-Historie und Proxy-Logs. Jetzt: HttpOnly/Secure/SameSite=Lax
  Cookie, Identität kommt ausschließlich aus dem Cookie (kein ?id=
  mehr nötig).
- Magic-Link- und Dashboard-Token hatten keine Ablauffrist. Jetzt:
  Magic-Link 7 Tage TTL, Session 30 Tage TTL (neue Tabellen
  magic_link_tokens, partner_sessions mit expires_at).
- Single-Use-Konsum mutierte audit_log.details — verstößt gegen
  Append-only-Prinzip für Audit-Trails. Jetzt: dedizierte
  magic_link_tokens-Tabelle mit consumed_at, audit_log bleibt
  vollständig unverändert/append-only.
- dashboard_token lag im Klartext in partners. Jetzt: nur SHA-256-
  Hashes in magic_link_tokens.token_hash und
  partner_sessions.session_token_hash, Klartext existiert nur
  transient im Mail-Link bzw. Cookie.
- Bonus: Re-Freigabe eines Partners (neue Tier-Stufe) widerruft jetzt
  automatisch alle bestehenden Sessions (revokeAllSessions).
- Token-Verifikation aus api/admin/approve.js in eigenes Modul
  src/core/auth/session.js ausgelagert (Separation of Concerns,
  von DeepSeek-R1 als Wartbarkeits-Punkt genannt).

31/31 Tests grün (2 neue: Re-Approve widerruft alte Session,
Session-Cookie-Attribute HttpOnly/Secure im Freigabe-Flow geprüft).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fix: DeepSeek-R1-Funde aus PR #2 behoben (alte Magic-Links, Cookie-Prefix)
All checks were successful
Unit & Contract Tests / Node.js Contract Tests (pull_request) Successful in 7s
371ad055ef
- approvePartner() invalidiert jetzt auch alle noch nicht eingelösten
  Magic-Link-Tokens des Partners (nicht nur die Sessions). Ohne das
  hätte ein vor einer Re-Freigabe verschickter, noch gültiger
  Magic-Link (7 Tage TTL) nach dem Revoke der Sessions weiterhin eine
  neue Session erzeugen können.
- Session-Cookie trägt jetzt das __Host--Präfix (erzwingt Secure,
  Path=/, kein Domain-Attribut) — verhindert Cookie-Tossing durch eine
  kompromittierte Subdomain von kurvenschule.cloud.
- Verdacht auf Prozessabsturz bei kaputt kodiertem Cookie-Wert
  (decodeURIComponent) empirisch geprüft und widerlegt: die äußere
  try/catch-Hülle in server.js fängt das sauber als 400 ab, kein Crash.

31/31 Tests grün (1 neuer Test: alter Magic-Link nach Re-Freigabe
ungültig, neuer funktioniert).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
andre merged commit a42e9dbd86 into main 2026-09-26 23:51:11 +02:00
andre deleted branch a2/session-model-hardening 2026-09-26 23:51:11 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
andre/kurven-schule-partner!2
No description provided.