diff options
| author | Adam Soltys <[email protected]> | 2026-03-13 23:16:11 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-03-13 23:16:11 -0700 |
| commit | a57046f80f8c85438ecf9549b94dc88146a976db (patch) | |
| tree | 1ca19c95eb9220897b52cb7034425c39416c6dd0 | |
| parent | 738eda87005232f3661a57daca3625d073a5c4aa (diff) | |
| download | coinos-ui-a57046f80f8c85438ecf9549b94dc88146a976db.tar.gz coinos-ui-a57046f80f8c85438ecf9549b94dc88146a976db.zip | |
fix: use 303 redirects in parse.ts and send layout to prevent POST forwarding
The parse function is called from both load functions (GET) and form
actions (POST). When called from an action, 307 redirects preserve the
POST method, causing the original form body to reach unrelated action
handlers in the redirect chain (e.g., send/invoice action receiving the
/send textarea body instead of payment data).
Changed all redirects in parse.ts and the send/bitcoin layout from 307
to 303, which converts POST to GET on redirect, preventing these
spurious action invocations.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
| -rw-r--r-- | src/lib/parse.ts | 30 | ||||
| -rw-r--r-- | src/routes/(app)/send/bitcoin/[address]/+layout.server.ts | 2 |
2 files changed, 16 insertions, 16 deletions
diff --git a/src/lib/parse.ts b/src/lib/parse.ts index 4705e6d5..e7fa6af2 100644 --- a/src/lib/parse.ts +++ b/src/lib/parse.ts @@ -14,10 +14,10 @@ export default async (s, host, cookies) => { let user; const aid = cookies.get("aid"); - if (t.startsWith("ark") || t.startsWith("tark")) redirect(307, `/send/ark/${t}${aid ? `?aid=${aid}` : ""}`); + if (t.startsWith("ark") || t.startsWith("tark")) redirect(303, `/send/ark/${t}${aid ? `?aid=${aid}` : ""}`); - if (t.startsWith("http")) redirect(307, t); - if (t.startsWith(host)) redirect(307, `http://${t}`); + if (t.startsWith("http")) redirect(303, t); + if (t.startsWith(host)) redirect(303, `http://${t}`); if (t.includes("lightning=")) { const url = new URL(t); @@ -37,8 +37,8 @@ export default async (s, host, cookies) => { t = t.split(":")[1]; } - if (["note", "nevent"].some((p) => t.startsWith(p))) redirect(307, `/e/${t}`); - if (["nprofile", "npub"].some((p) => t.startsWith(p))) redirect(307, `/${t}`); + if (["note", "nevent"].some((p) => t.startsWith(p))) redirect(303, `/e/${t}`); + if (["nprofile", "npub"].some((p) => t.startsWith(p))) redirect(303, `/${t}`); // bitcoin if (t.toLowerCase().startsWith("bitcoin:") || t.toLowerCase().startsWith("liquidnetwork:")) { @@ -48,7 +48,7 @@ export default async (s, host, cookies) => { if (options.ark) { let r = `/send/ark/${options.ark}`; if (amount) r += `/${Math.round(Number(amount) * sats)}`; - redirect(307, r); + redirect(303, r); } t = addr; @@ -58,12 +58,12 @@ export default async (s, host, cookies) => { if (validate(t) || isLiquid(t) || t.startsWith("bcrt1")) { let r = `/send/bitcoin/${t}`; if (amount) r += `/${Math.round(amount * sats)}`; - redirect(307, r); + redirect(303, r); } // lightning if (t.toLowerCase().startsWith("lightning:")) t = t.toLowerCase().replace("lightning:", ""); - if (t.toLowerCase().startsWith("lnurl")) redirect(307, `/ln/${t}`); + if (t.toLowerCase().startsWith("lnurl")) redirect(303, `/ln/${t}`); if (t.includes(":")) t = t.split(":")[1]; if (t.toLowerCase().startsWith("lnr")) { @@ -80,11 +80,11 @@ export default async (s, host, cookies) => { const { offer_amount_msat: a } = await get(`/decode/${t}`); if (a) ({ invoice: t } = await post("/fetchinvoice", { offer: t })); } - redirect(307, `/send/lightning/${t}`); + redirect(303, `/send/lightning/${t}`); } } - if (invoice) redirect(307, `/invoice/${invoice.id}`); + if (invoice) redirect(303, `/invoice/${invoice.id}`); if (t.match(/^🥜([\uFE00-\uFE0F]|[\uE0100-\uE01EF]+)$/)) { t = (Array.from(t) as string[]) @@ -109,10 +109,10 @@ export default async (s, host, cookies) => { if (t.startsWith("cashu")) { const { id } = await post("/cash", { token: t }); - redirect(307, `/ecash/${id}`); + redirect(303, `/ecash/${id}`); } - if (t.startsWith("creq")) redirect(307, `/send/ecash/${t}`); + if (t.startsWith("creq")) redirect(303, `/send/ecash/${t}`); // user try { @@ -120,14 +120,14 @@ export default async (s, host, cookies) => { if (user.anon) user = null; } catch {} - if (user) redirect(307, `/pay/${t}`); + if (user) redirect(303, `/pay/${t}`); let fund; - if (t.includes("/fund")) redirect(307, t.substring(t.indexOf("/fund"))); + if (t.includes("/fund")) redirect(303, t.substring(t.indexOf("/fund"))); try { fund = await get(`/fund/${t}`); } catch {} - if (fund) redirect(307, `/send/fund/${t}`); + if (fund) redirect(303, `/send/fund/${t}`); }; diff --git a/src/routes/(app)/send/bitcoin/[address]/+layout.server.ts b/src/routes/(app)/send/bitcoin/[address]/+layout.server.ts index e69956dc..8c46b7b9 100644 --- a/src/routes/(app)/send/bitcoin/[address]/+layout.server.ts +++ b/src/routes/(app)/send/bitcoin/[address]/+layout.server.ts @@ -21,7 +21,7 @@ export async function load({ cookies, params, parent }) { // Vault account — stay on /send/bitcoin for on-chain send } else { // Custodial account — send internally via the original invoice - if (invoice.uid !== user.id) redirect(307, `/send/invoice/${invoice.id}`); + if (invoice.uid !== user.id) redirect(303, `/send/invoice/${invoice.id}`); } } |
