Add an admin-issued magic link that bypasses Discord login #503
No reviewers
Labels
No labels
Kestra
bug
enhancement
someday
subtask
☁️ api
🎛️ infrastructure
🐞 sentry
📆 2025 Season
📝 pages
allpicks
📝 pages
picks
📝 pages
standings
🚀 performance
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
johnsturgeon/tgfp-web!503
Loading…
Reference in a new issue
No description provided.
Delete branch "add-the-ability-for-the-admin-to-send-a-magic-link-live-for-24-hours-to-bypass-login-502"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Discord OAuth is the only way into the pool, so a player who loses access
to their Discord account -- 2FA on a phone that stops receiving SMS the
moment they leave the country -- has no route back in and nothing they can
do about it themselves.
A key button next to the welcome button on Admin -> Players emails that
player a link good for 24 hours. The token is their Discord id, signed with
SESSION_SECRET_KEY, so there is no table, no row to expire and nothing to
clean up; pressing the button twice is harmless. Redeeming it sets exactly
the cookie the OAuth callback sets, which is now minted in one shared
helper so the two ways in cannot drift apart.
The id inside the token is readable, which costs nothing: tgfp-discord-id
is itself an unsigned cookie holding that same id, so the link discloses
nothing its own destination does not hand over. The signature is the part
that matters, and it is what stops a link for one player being edited into
a link for another.
The button is disabled for a Pending player: the link resolves through
by_discord_id, so a NULL there would make it a dead end rather than a login.
resolves #502
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
This is a clean, well-tested change. Here is my review.
Review: Add an admin-issued magic link that bypasses Discord login
This is a well-scoped, thoroughly tested feature: the stateless signed-token design is sound, the cookie-minting consolidation into
_login_cookieis a genuine improvement, and the dev-recipient rewrite is both refactored and fixed for display-name addresses. I found no blocking issues; a few minor points below.Worth fixing
httponly/secure/samesiteflags —app/routers/auth.py:77-81. This is pre-existing behavior extracted verbatim, but this PR is the moment the app's only session-identifying cookie (tgfp-discord-id) gets minted in exactly one place, so it's the natural place to harden it. Withouthttponly, any XSS can exfiltrate the cookie; withoutsecure, it can traverse plain HTTP. Addinghttponly=True, secure=True, samesite="lax"here fixes both login paths at once. Out of strict scope, but cheap and central now.Nits
read_magic_tokencatches(BadSignature, ValueError)but not the broaderitsdangerous.BadData—app/magic_link.py:27. In practiceBadPayload/BadDatais unreachable here (an attacker can't produce a valid signature to reach payload deserialization, and expired/tampered tokens raiseSignatureExpired/BadSignature, both subclasses ofBadSignature), so this is not a correctness bug. CatchingBadDatawould just be marginally more robust and self-documenting.No test for the
magic_failedbranch —app/routers/v2.py:572-573. The mailer-raises path (ConnectionError/OSError→magic_failed) is untested.resend_welcomehas the same gap, so this only matches existing coverage rather than falling short of it, but a monkeypatchedsend_magic_link_emailthat raises would close it in a couple of lines.Neither login path checks
player.active—app/routers/auth.py:114(and the OAuth callback at:97). A deactivated player who still has a Discord id and a row can be issued and redeem a magic link, and the admin button is enabled for them. This is consistent with the existing OAuth behavior, so it's not a regression — just worth a conscious decision about whether the key button should be gated onactivetoo.Unclosed
<li>tags inapp/templates/email_magic_link.j2:20-27— valid HTML5 (closing tags are optional), purely cosmetic.Nice touches worth calling out: the stateless token means email link-scanners/prefetchers can't burn the link, the Pending-player dead-end is handled explicitly and tested, and the tamper/forged-key/expiry cases all have direct unit tests.