aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-03-13 23:16:11 -0700
committerAdam Soltys <[email protected]>2026-03-13 23:16:11 -0700
commita57046f80f8c85438ecf9549b94dc88146a976db (patch)
tree1ca19c95eb9220897b52cb7034425c39416c6dd0
parent738eda87005232f3661a57daca3625d073a5c4aa (diff)
downloadcoinos-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.ts30
-rw-r--r--src/routes/(app)/send/bitcoin/[address]/+layout.server.ts2
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}`);
}
}