aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-27 14:48:24 -0700
committerAdam Soltys <[email protected]>2026-09-27 14:48:24 -0700
commitb1e175c1faa46ed67cf0575f8d8808f1ae10892c (patch)
tree4672affbc88789b4f5d17e5d92ffca8db49248b0
parent0912f443cc738af7dd7093670f00420de72a095f (diff)
downloadcoinos-server-b1e175c1faa46ed67cf0575f8d8808f1ae10892c.tar.gz
coinos-server-b1e175c1faa46ed67cf0575f8d8808f1ae10892c.zip
ecash: drop anonymous token storage in favour of a stateless status lookup
POST /cash stored any submitted string under a fresh id with no auth, no validation and no expiry. Replace it with POST /ecash/status, which checks a token the client already holds and returns its total, spent amount and mint. GET /cash/:id/:version stays so previously shared ids keep resolving. Co-Authored-By: Claude Fable 5.1 <[email protected]>
-rw-r--r--index.ts2
-rw-r--r--routes/ecash.ts14
2 files changed, 9 insertions, 7 deletions
diff --git a/index.ts b/index.ts
index 2c386556..22854661 100644
--- a/index.ts
+++ b/index.ts
@@ -198,7 +198,7 @@ app.post("/shopify/:id", shopify);
app.get("/bolt12", fixBolt12);
app.get("/cash/:id/:version", ecash.get);
-app.post("/cash", ecash.save);
+app.post("/ecash/status", ecash.status);
app.post("/claim", auth, ecash.claim);
app.post("/ecash/:id", ecash.receive);
diff --git a/routes/ecash.ts b/routes/ecash.ts
index d089ed15..995a8dce 100644
--- a/routes/ecash.ts
+++ b/routes/ecash.ts
@@ -1,19 +1,21 @@
-import { s } from "$lib/db";
import { check, get, redeem } from "$lib/ecash";
import { err } from "$lib/logging";
import { bail, fail, getInvoice, getUser } from "$lib/utils";
import { getEncodedToken } from "@cashu/cashu-ts";
-import { v4 } from "uuid";
export default {
- async save(req, res) {
+ // Stateless status lookup for a token the client is holding. This replaced
+ // an unauthenticated POST that stored any submitted string under a fresh id
+ // forever (thousands of forged and foreign tokens accumulated); `get` below
+ // still serves the ids that were handed out while it existed.
+ async status(req, res) {
const {
body: { token },
} = req;
try {
- const id = v4();
- await s(`cash:${id}`, token);
- res.send({ id });
+ if (typeof token !== "string" || !token.startsWith("cashu"))
+ fail("Invalid token");
+ res.send(await check(token));
} catch (e) {
err(e.message);
bail(res, e.message);