Artifacts in openspec/changes/admin-link-email/.
What
- Optional email field on
/create; empty field = exactly today's behavior, invalid address rejected.
- With an email: one transactional email (Cloudflare Email Service
send_email binding, best-effort, address never stored) in the poll's language carrying the organizer link and an 8-char admin code.
- The code gates organizer pages once per browser (HttpOnly cookie remembers it; creator's browser unlocked at creation). New nullable
events.admin_code column — NULL = ungated, existing polls untouched.
Why
The organizer URL is the only admin credential. Its token (~131 bits) is not brute-forceable, but URLs leak — history, logs, screen shares, forwarded screenshots. The code is a second secret so a leaked URL alone no longer grants admin access. Trade-off (accepted): link and code travel in the same email, so this defends against URL leakage, not a compromised inbox.
Artifacts in
openspec/changes/admin-link-email/.What
/create; empty field = exactly today's behavior, invalid address rejected.send_emailbinding, best-effort, address never stored) in the poll's language carrying the organizer link and an 8-char admin code.events.admin_codecolumn —NULL= ungated, existing polls untouched.Why
The organizer URL is the only admin credential. Its token (~131 bits) is not brute-forceable, but URLs leak — history, logs, screen shares, forwarded screenshots. The code is a second secret so a leaked URL alone no longer grants admin access. Trade-off (accepted): link and code travel in the same email, so this defends against URL leakage, not a compromised inbox.