diff options
| -rw-r--r-- | lib/invoices.ts | 11 | ||||
| -rw-r--r-- | routes/invoices.ts | 10 |
2 files changed, 21 insertions, 0 deletions
diff --git a/lib/invoices.ts b/lib/invoices.ts index ba55b77c..7f25f4ba 100644 --- a/lib/invoices.ts +++ b/lib/invoices.ts @@ -84,6 +84,17 @@ export const generate = async ({ invoice, user }) => { if (memo && memo.length > 5000) fail("memo too long"); if (!id) id = v4(); + else { + // A caller-supplied id re-uses an existing record (tip/webhook update, + // repeated bolt12 offer create). It must be the caller's own: the write + // below replaces `invoice:<id>` wholesale with uid = this user, and the + // old bolt11 pointer keeps resolving to the same id, so overwriting + // another user's invoice re-routes whatever their payer sends into this + // account. Same error as a missing invoice so ids can't be probed for + // existence. + const existing = await getInvoice(id); + if (existing && existing.uid !== user.id) fail("invoice not found"); + } let hash; let text; diff --git a/routes/invoices.ts b/routes/invoices.ts index 9ba45af6..b8d06bba 100644 --- a/routes/invoices.ts +++ b/routes/invoices.ts @@ -36,6 +36,16 @@ export default { else invoice.own = false; try { + // A supplied id that already exists is an UPDATE of that record — + // generate() rewrites it wholesale. Only the authenticated owner may do + // that. Without this, an anonymous pay-page session passing + // body.user = merchant could rewrite the amount/memo of a merchant's + // live invoice. generate() separately refuses to move an invoice to a + // different uid. + if (invoice?.id) { + const existing = await getInvoice(invoice.id); + if (existing && existing.uid !== req.user.id) fail("invoice not found"); + } const result = await generate({ invoice, user }); res.send(result); } catch (e) { |
