Dataroom routes were reading x-investor-id from request headers which
the middleware sets as response headers — these don't reach route handlers
when the admin fallback path runs (NextResponse.next() without header).
Switch to getSessionFromCookie() consistent with all other investor routes.
Auth page DSGVO footer switched from absolute bottom-0 to normal flow
so the expanded Art. 13 notice doesn't overlap the login card.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- runDataCleanup() replaces maskOverdueInvestors(): now also anonymizes
never-activated invites after 90 days, deletes sessions + magic links
older than 30 days, NULLs IPs in audit logs older than 30 days, and
redacts email from audit log details JSONB for masked investors
- New /api/admin/cleanup POST endpoint for scheduled invocation
- New .gitea/workflows/pitch-cleanup.yml: daily cron at 02:00 UTC calls
the cleanup endpoint so anonymization is genuinely automatic, not lazy
- Switch masking window from first_activity_at to last_login_at (30 days
of inactivity; resets on each login)
- Both auth pages: DSGVO footer now covers all Art. 13 requirements —
data categories, retention cutoffs, Art. 15–21 rights, contact address,
LfDI Baden-Württemberg as supervisory authority
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Investors who lost their session or whose invite token was already used
can now enter their email on /auth to receive a fresh access link,
without needing a manual re-invite from an admin.
- New /api/auth/request-link endpoint looks up the investor by email,
issues a new pitch_magic_links row, and emails the link via the
existing sendMagicLinkEmail path. Response is generic regardless of
whether the email exists (enumeration resistance) and silently no-ops
for revoked investors.
- Rate-limited both per-IP (authVerify preset) and per-email (magicLink
preset, 3/hour — same ceiling as admin-invite/resend).
- /auth page now renders an email form; submits to the new endpoint and
shows a generic "if invited, link sent" confirmation.
- Route-level tests cover validation, normalization, unknown email,
revoked investor, and both rate-limit paths.
- End-to-end regression test wires request-link + verify against an
in-memory fake DB and asserts the full flow: original invite used →
replay rejected → email submission → fresh link → verify succeeds.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>