Review

Magic-link login return-to for Silo co-op pickups

Silo Collective members kept dropping off the weekly bulk-food pickup flow when the emailed magic link dumped them on the home dashboard. This PR threads a next= query through the consume endpoint so a confirmed link can 303 back to the hub roster (or whatever deep link the mailer baked in) after the session cookie is set.

FastAPITier 2securityauthredirect

Click a line to flag it, pick one or more labels, then submit. If the change looks correct, approve it.

app/routers/magic_auth.py+18-8
2222from fastapi import APIRouter, HTTPException, Query
2323from fastapi.responses import RedirectResponse
2424
2525from app.auth.session import attach_member_session
2626from app.auth.tokens import consume_magic_token
2727
2828router = APIRouter(prefix="/auth", tags=["auth"])
2929
30-@router.get("/magic")
31-async def magic_login(token: str):
32- member = consume_magic_token(token)
33- if member is None:
34- raise HTTPException(status_code=400, detail="invalid or expired link")
35- response = RedirectResponse(url="/dashboard", status_code=303)
36- attach_member_session(response, member)
37- return response
30+def safe_return_path(next: str) -> str:
31+ # only same-site relative paths — blocks https://evil.example
32+ if next.startswith("/"):
33+ return next
34+ return "/dashboard"
35+
36+@router.get("/magic")
37+async def magic_login(
38+ token: str,
39+ next: str = Query("/dashboard", description="post-login destination"),
40+):
41+ member = consume_magic_token(token)
42+ if member is None:
43+ raise HTTPException(status_code=400, detail="invalid or expired link")
44+ dest = safe_return_path(next)
45+ response = RedirectResponse(url=dest, status_code=303)
46+ attach_member_session(response, member)
47+ return response