{
  "version": 3,
  "sources": ["login-link.js"],
  "sourcesContent": ["// A sign-in link that carries its token in the URL (/account/login/?token_hash=\n// ...&type=...). Launch review SEC-23, 2026-09-13.\n//\n// Verifying a token_hash signs the browser into whichever account the token\n// belongs to. So a link someone else minted for THEIR account can sign a\n// visitor into it (login CSRF): anything the visitor then uploads or types\n// lands in the attacker's account. The review rejected a browser-bound nonce,\n// because a real emailed link is often opened on another device, and that must\n// keep working. The fix is an explicit confirmation instead:\n//\n//   - already signed in as someone: ask before switching accounts, and staying\n//     put never spends the link;\n//   - after the link verifies: say which account this is, and offer to sign\n//     straight back out if it is not theirs.\n//\n// Dependencies are injected so the flow can be tested without a browser:\n//   auth  { getSession(), verifyOtp({token_hash, type}), signOut({scope}) }\n//   ui    { confirm(text, yesLabel, noLabel) → Promise<boolean>,\n//           status(message, kind), clearUrl(), go(url) }\n// Returns \"stayed\" | \"failed\" | \"declined\" | \"continued\".\nexport async function runTokenHashSignIn({ auth, tokenHash, type, next, ui }) {\n  let current = null;\n  try { current = (await auth.getSession())?.data?.session?.user?.email || null; } catch { current = null; }\n\n  if (current) {\n    const switchIt = await ui.confirm(\n      `You're signed in as ${current}. This link signs you in to the account it was sent to instead.`,\n      \"Switch accounts\",\n      `Stay signed in as ${current}`,\n    );\n    if (!switchIt) {\n      ui.clearUrl();\n      ui.go(next);\n      return \"stayed\";\n    }\n  }\n\n  ui.status(\"Signing you in…\", \"info\");\n  let data = null, error = null;\n  try { ({ data, error } = await auth.verifyOtp({ token_hash: tokenHash, type })); }\n  catch (e) { error = e; }\n  // Spent or dead, the token leaves the address bar so Back never replays it.\n  ui.clearUrl();\n  if (error || !data?.session) {\n    ui.status(`That sign-in link didn't work${error?.message ? ` (${error.message})` : \"\"}. It may have been used already or expired. Sign in below.`, \"error\");\n    return \"failed\";\n  }\n\n  const who = data.session.user?.email || data.user?.email || \"this account\";\n  ui.status(`Signed in as ${who}.`, \"success\");\n  const keep = await ui.confirm(`You're signed in as ${who}.`, `Continue as ${who}`, \"That's not me, sign out\");\n  if (!keep) {\n    try { await auth.signOut({ scope: \"local\" }); } catch { /* best effort */ }\n    ui.status(\"Signed out. Sign in below with your own account.\", \"info\");\n    return \"declined\";\n  }\n  ui.go(next);\n  return \"continued\";\n}\n"],
  "mappings": "AAoBA,sBAAsB,mBAAmB,CAAE,KAAAA,EAAM,UAAAC,EAAW,KAAAC,EAAM,KAAAC,EAAM,GAAAC,CAAG,EAAG,CAC5E,IAAIC,EAAU,KACd,GAAI,CAAEA,GAAW,MAAML,EAAK,WAAW,IAAI,MAAM,SAAS,MAAM,OAAS,IAAM,MAAQ,CAAEK,EAAU,IAAM,CAEzG,GAAIA,GAME,CALa,MAAMD,EAAG,QACxB,uBAAuBC,CAAO,kEAC9B,kBACA,qBAAqBA,CAAO,EAC9B,EAEE,OAAAD,EAAG,SAAS,EACZA,EAAG,GAAGD,CAAI,EACH,SAIXC,EAAG,OAAO,kBAAmB,MAAM,EACnC,IAAIE,EAAO,KAAMC,EAAQ,KACzB,GAAI,EAAG,CAAE,KAAAD,EAAM,MAAAC,CAAM,EAAI,MAAMP,EAAK,UAAU,CAAE,WAAYC,EAAW,KAAAC,CAAK,CAAC,EAAI,OAC1EM,EAAG,CAAED,EAAQC,CAAG,CAGvB,GADAJ,EAAG,SAAS,EACRG,GAAS,CAACD,GAAM,QAClB,OAAAF,EAAG,OAAO,gCAAgCG,GAAO,QAAU,KAAKA,EAAM,OAAO,IAAM,EAAE,6DAA8D,OAAO,EACnJ,SAGT,MAAME,EAAMH,EAAK,QAAQ,MAAM,OAASA,EAAK,MAAM,OAAS,eAG5D,GAFAF,EAAG,OAAO,gBAAgBK,CAAG,IAAK,SAAS,EAEvC,CADS,MAAML,EAAG,QAAQ,uBAAuBK,CAAG,IAAK,eAAeA,CAAG,GAAI,yBAAyB,EACjG,CACT,GAAI,CAAE,MAAMT,EAAK,QAAQ,CAAE,MAAO,OAAQ,CAAC,CAAG,MAAQ,CAAoB,CAC1E,OAAAI,EAAG,OAAO,mDAAoD,MAAM,EAC7D,UACT,CACA,OAAAA,EAAG,GAAGD,CAAI,EACH,WACT",
  "names": ["auth", "tokenHash", "type", "next", "ui", "current", "data", "error", "e", "who"]
}
