aboutsummaryrefslogtreecommitdiffstats
path: root/routes
Commit message (Collapse)AuthorAgeFilesLines
* logging: redact auth headers; set cookie flags on session token (S5)Adam Soltys2026-08-071-2/+14
| | | | | | | | | | | The request logger persisted full req.headers to the "req" log, including the Authorization bearer token and the token cookie — both non-expiring JWTs, so log-read access became account takeover. Redact the authorization and cookie headers. Also set httpOnly/secure/sameSite on the token cookie so it isn't readable by injected JS or sent over plaintext HTTP. (Revocation is already handled by the evicted set.) Co-Authored-By: Claude Opus 4.8 <[email protected]>
* auth: fail closed on adminpass, scrub sample secrets (S3)Adam Soltys2026-08-073-4/+9
| | | | | | | | | | | | | | | | The master-password path (isAdmin = password === config.adminpass) logs in as any user, skipping password, 2FA, and captcha. Worse, when adminpass is unset an omitted password field made undefined === undefined true, authenticating as anyone. Require both the configured value and the supplied password to be non-empty and match exactly, across every adminpass comparison (login, recaptcha bypass, support email, freeze). Also scrub config.ts.sample: the sample shipped a working-looking JWT secret, two server nsec keys, and adminpass "pwa". Replace with fail-closed placeholders and regenerate instructions. Rotate any deployment that ever shipped the sample values. Co-Authored-By: Claude Opus 4.8 <[email protected]>
* auth: hash passwords at bcrypt cost 12 with transparent upgrade (S2)Adam Soltys2026-08-071-2/+17
| | | | | | | | | | Registration, password change, and admin reset all hashed at bcrypt cost 4 (16 rounds, ~1,300 guesses/s/thread) — the single factor gating the custodial balance and the nip49 passphrase for the stored nostr key. Raise all three sites to cost 12, and on successful login re-hash any legacy cost-4 hash now that we hold the verified plaintext. Co-Authored-By: Claude Opus 4.8 <[email protected]>
* sockets: verify JWT signatures on the websocket auth path (S1)Adam Soltys2026-08-071-4/+4
| | | | | | | | | | | | | | The websocket auth gate used jwt.decode(), which parses the payload but never checks the signature — anyone could forge {id: <victim uid>} (alg:none or any key), register their socket under the victim's uid, and receive every emit for that account. Switch both track() and the "login" gate to jwt.verify(token, config.jwt), rejecting forged/altered tokens. Also project the full-record socket emits through pick(user, whitelist) so a "user" event can never carry the bcrypt hash, TOTP secret, or PIN — matching the shape /login and /me already return. Co-Authored-By: Claude Opus 4.8 <[email protected]>
* lnurl: defer to the v3 registrar for names it has claimedAdam Soltys2026-08-041-15/+53
| | | | | | | | | | | | | | Serving local accounts unconditionally sent migrated names (whose legacy user record still exists) back to the old account, and left v3-only custom names 500ing — only npub1* names had a Cloudflare redirect. Resolution order is now: registrar-claimed -> registrar (response handed through, cached 60s, fail-open to local if the registrar is down), else local account, else 404. The migrated flag stays out of it: register.ts stamps it on essentially every account. No loop: we defer only for names the registrar HAS, and it never queries us for those. Co-Authored-By: Claude Fable 5 <[email protected]>
* lnurl: serve local accounts directly instead of deferring to v3 registrarAdam Soltys2026-08-041-19/+9
| | | | | | | | | | | | | | | | | A recent change made this coinos.io lnurlp endpoint defer any migrated-flagged account to the v3 names registrar and 404 when the registrar didn't have the name. But registration stamps that flag on essentially every account and never syncs names to the registrar, so this broke lightning-address resolution for nearly all accounts. The v3 registrar is the front for v3 accounts and falls back to this endpoint, so this endpoint is the fallback target: it must serve its own accounts directly and must not defer back (which also avoids a loop once the registrar's fallback is live). Serve the local account; 404 only when no local account exists. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01Dpqu6wizYe1c1g4k5Gd8JK
* Let a migrated account hand its address to coinos v3Adam Soltys2026-08-032-4/+5
| | | | | | | | | | | | A `migrated` flag on the user, settable through /user and visible on the public user endpoint. It changes exactly one thing: lnurlp for that username defers to the names registrar, so payments to [email protected] arrive in the person's v3 wallet. The account itself is untouched — same username, same history, still usable here — which is why this is a flag and not a rename or a shutdown. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* LNURL: fall back to the v3 names registrar for unknown usersAdam Soltys2026-08-021-1/+21
| | | | | | | | | An address at this domain may now belong to a coinos v3 wallet rather than a local account. When the username isn't ours, ask the names registrar and pass its payRequest through unchanged — its callback is absolute, so the payer talks to it directly from there. Co-Authored-By: Claude Fable 5 <[email protected]>
* confirm: guard the first-seen deposit credit atomically (COINOS-2)Adam Soltys2026-07-311-0/+8
| | | | | | | | | | | | | | | | /confirm is unauthenticated, and its first-seen branch credited a deposit after a plain getPayment() read with no lock. Concurrent callers (walletnotify, catchUp, bulk sweeps, or anyone posting the endpoint) all read p == null for one deposit and each ran credit(), inflating both the pending balance and the spendable credit:<net>:<uid> fee accumulator — a fee waiver nobody paid for. Verified on regtest: 10 concurrent /confirm on one 100,000 sat deposit credited 1,000,000 pending and 4,000 fee credit. With the NX guard, exactly 100,000 and 400. Mirrors the lock the confirm stage already had. Reported-by: Muhammed Shekho (@SYR-ROOT) <[email protected]> Co-Authored-By: Claude Fable 5 <[email protected]>
* take: claim single-use authorizations atomically (COINOS-3)Adam Soltys2026-07-311-17/+38
| | | | | | | | | | | | | | | | | | | take() enforced single-use by reading authorization.claimed, checking it, then writing it back — with yielding awaits in between and no lock. N concurrent POST /take for one authorization all passed the gate, so each debited the authorizer and funded the pool. Verified on regtest: 10 concurrent takes redeemed one 10,000 sat single-use authorization 5 times (50,000 debited from the authorizer, 50,000 credited to the caller). With the SET NX claim, exactly one wins. Funding also runs inside a try that releases the claim on failure and only marks the record claimed once the money has landed — otherwise a funding that throws (insufficient balance, server limit) burns the authorization: claim key set, fund never funded, every later take skipping the funding block. Reported-by: Muhammed Shekho (@SYR-ROOT) <[email protected]> Co-Authored-By: Claude Fable 5 <[email protected]>
* mint/lightning: refuse caller-supplied invoices, tiered fees, log hygieneAdam Soltys2026-07-301-3/+11
| | | | | | | | | | | | | | | - invoices: reject caller-supplied bolt11/bolt12; the server always mints its own. Closes the invoice-registration path behind the shared-node credit and payment-hijack vectors. - lightning: skip mint-owned invoices (lbl* label) in the paid-invoice listener. - payments: lightning free-tier fee credit, withheld for flagged high-throughput (ln:nofree) accounts which pay an elevated rate; the failed-send reversal restores credit at the same rate. freezeCheck made crash-resilient. Lightning and bolt12 sends stay pending until the preimage settles. - payments (route): validate payment-request / invoice input types. - logging: collapse repeated xpay failure lines into one. Co-Authored-By: Claude Opus 4.8 <[email protected]>
* bolt12 zaps: pay offers, NWC pay/receive, per-user offers, NIP-44Adam Soltys2026-07-302-35/+74
| | | | | | | | | | | | | | | | - sendLightning fetches an invoice from lno1 offers (payer_note passthrough for NIP-177 zap intents) and pays it through the existing bolt12 path - NWC: new nwc#2 pay/receive methods (BIP-321 URIs, payer proofs stubbed until CLN 26.06 createproof), nip44_v2 alongside nip04 with per-request scheme detection, dual 23196/23197 notifications, 13194 refreshed when its content or encryption tag goes stale - standing per-user bolt12 offer via GET /offer; incoming bolt12 payments record invreq_payer_note as memo - parse/fetchinvoice routes handle offers; zap display parses kind 9736 receipts (amount tag + embedded intent) and drops a call to a sync() helper that never existed Co-Authored-By: Claude Fable 5 <[email protected]>
* preimages: support fiat prices, converted at invoice-creation timeAdam Soltys2026-07-141-8/+25
| | | | | | | | | | | | | | POST /preimages now accepts fiat + optional currency (defaults to the merchant's) as an alternative to a sat price. The fiat amount is stored per entry and converted to sats at the moment a usePreimage invoice is created, so the price tracks the exchange rate instead of freezing at upload time. The invoice also takes on the entry's currency and rate so receipts display the fiat price exactly as the merchant set it. If no rate is available for the entry's currency, the preimage is pushed back onto the queue and creation fails rather than issuing an any-amount invoice. Co-Authored-By: Claude Fable 5 <[email protected]>
* preimages: enforce a merchant-set price per preimage batchAdam Soltys2026-07-131-11/+24
| | | | | | | | | | | | | Invoice creation is unauthenticated, so buyers could name their own price for a queued preimage (create the usePreimage invoice with amount: 1). POST /preimages now takes an optional price (sats) that is stored per entry and overrides the requested amount when the preimage is consumed; omitting it keeps the old any-amount behavior. Also gate the stored-preimage reveal in credit() on the invoice being fully paid, since internal sends can settle partial amounts. Bare-hex entries queued before this change still parse (treated as priceless). Co-Authored-By: Claude Fable 5 <[email protected]>
* Merge remote-tracking branch 'origin/master'Adam Soltys2026-07-111-0/+76
|\ | | | | | | | | # Conflicts: # lib/payments.ts
| * invoices: sell merchant-supplied preimages via a usePreimage queueAdam Soltys2026-07-101-0/+76
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Merchants POST /preimages (auth) to pre-load 32-byte hex preimages into a FIFO queue; creating an invoice with usePreimage: true consumes the next one so the invoice's payment hash is sha256(preimage). Paying the invoice reveals the merchant-chosen secret to the buyer — via their own wallet on external settlements, and via GET /invoice/:id once settled — enabling backend-less digital download stores (encrypted files on static hosting, preimage + public string = decryption key). - GET /preimages lists the queue with payment hashes so the merchant can map files to future invoices; POST /preimages/delete clears it - empty queue fails invoice creation rather than issuing a random preimage the buyer would pay for but couldn't decrypt with - preimages cln rejects as already used are discarded and the next tried - consumed preimages persist at preimage:${payment_hash} (TTL = invoice expiry) and credit() prefers them as the settlement ref, so internal coinos-to-coinos payments reveal the real preimage instead of the sender's uid Co-Authored-By: Claude Fable 5 <[email protected]>
* | payments: use the debited tip when crediting internal transfersAdam Soltys2026-07-102-4/+15
|/ | | | | | | | | | | | | | | | credit() now credits the tip the sender was actually debited (passed in by the internal-transfer callers) instead of independently re-reading the invoice. This closes a time-of-check/time-of-use gap where an invoice's tip could be changed between the sender debit and the receiver credit, letting a payer be credited a tip they were never charged. External receipts (lightning/ecash) keep reading the invoice tip, since there is no paired coinos debit to diverge from. Also harden POST /invoice/:id: validate the tip is a sane non-negative integer and reject tip changes on an already-settled invoice. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* users: return generic 404 from /reset for non-adminsAdam Soltys2026-06-271-1/+10
| | | | | | | | | The admin-only password-reset endpoint returned "disabled" to non-admin callers, which confirmed both that the endpoint exists and that it is admin-gated. Return a generic 404 identical to an unregistered route instead; still log the rejected attempt server-side for monitoring. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* lnurl: SSRF-guard user-supplied URL fetches + validate zap eventsAdam Soltys2026-06-151-5/+9
| | | | | | | | | | | | | | | | The /lnurl/proxy, /decode and /encode handlers fetched an arbitrary user-supplied URL via got() and returned the body — an unauthenticated SSRF reaching internal services. Route those fetches through a new safeGot() that rejects hosts resolving to loopback/private/link-local/ CGNAT/metadata ranges, re-validates on each redirect hop, restricts the scheme to http(s), and caps timeout + response size. Also validate the NIP-57 zap request (kind 9734 + signature) before storing it, instead of trusting the client-supplied event. These endpoints can't be removed — they back lightning-address/LNURL parsing and resolution in the wallet UI. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* users: add authenticated self-delete endpointAdam Soltys2026-06-131-0/+103
| | | | | | | | | | POST /delete (auth) wipes every key tied to the calling user, mirroring scripts/delall-legacy.ts: user records, payments/invoices, sub-accounts, NWC apps, nostr follow graph + nip5. Gated by typing the username and refuses while balance+pending > 10k sats so funds can't be destroyed by accident. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* nwc: keep notifications opt-in (revert default-on)Adam Soltys2026-06-061-6/+5
| | | | | | | | | | Reverting the notify default-on from 8cb18821 — most users don't consume notifications and defaulting on would flood the relay with payment_sent/ payment_received for ~3k apps. Notifications stay opt-in: clients enable by sending notify:"true" when creating the NWC connection. The pay_invoice reply-reliability and rate-limit fixes from 8cb18821 are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* nwc: fix pay_invoice reply reliability, rate-limit drops, notificationsAdam Soltys2026-06-061-1/+6
| | | | | | | | | | | | | | | | | | | | | | | | | Three issues reported by an NWC client integrator (refs #80): (a) pay_invoice sync reply: the handler polled listpays for ~20s then returned a misleading "Payment timed out" error on slow routes even though the payment settled — client never got the preimage. sendLightning already runs xpay to completion and finalize() sets p.ref = preimage, so return that directly; keep a longer listpays loop only as a fallback, and on still-in-flight return an explicit "check with lookup_invoice" rather than implying failure. (b) rate limiting: raised per-pubkey cap 5/min -> 60/min (normal usage — a balance check + payment + a few lookup_invoice reconciles — trivially exceeded 5), and over-limit requests now get a NIP-47 RATE_LIMITED error reply instead of a silent drop that looked exactly like a lost reply. Also: every request path now always sends a 23195 reply (success, error, or handler-returned- nothing) so a client never hangs waiting. (c) notifications: payment_received/payment_sent are advertised in the info event but app.notify defaulted to false unless the client sent notify:"true" (only ~1% of apps had it on), so they never fired. Default notify ON; only disable when explicitly "false". (Existing apps still false — backfill TBD.) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* mirror payment records to arc + atomic /confirm lock to prevent double-creditsAdam Soltys2026-05-171-3/+27
| | | | | | | | | | | | | | | | | | | | | | | | Follows up the Apr 29 double-credit incident. Two parallel callers of /confirm (walletnotify, catchUp, bulk sweeps) could each read p.confirmed=false and each run the multi() that increments balance — because main-db pointers were sometimes missing while arc still had them, gf()'s arc fallback returned stale state. Three pieces here: - routes/payments.ts: SET NX EX 60 lock around the /confirm credit block keyed by `confirmlock:${txid}:${vout}`. Concurrent callers short-circuit cleanly. The lock is released on success and on every early-return. - lib/payments.ts credit() + routes/payments.ts /confirm: mirror the payment/invoice records and the `payment:<txid>:<vout>` / `payment:<hash>` pointer keys into arc as soon as they're written. gf() will then find the prior credit via arc fallback even if the main-db copy is wiped by a future data-loss event. - routes/payments.ts hide `revertedDuplicate` payments from the user's payment list — matches the same filter added to lib/nwc.ts list_transactions so reverted records never surface in either rendering path. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
* remove /gateway endpointAdam Soltys2026-05-171-9/+0
| | | | | | | | | Unauthenticated arbitrary db.set abuse vector. Exploited to inflate user balances and override safety controls (bitcoin:limit, limit, hardfreeze, freeze) in real time, leading to a hot-wallet drain. Route and handler both fully removed. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
* lnurl proxyAdam Soltys2026-03-271-0/+12
|
* don't remove missing paymentsAdam Soltys2026-03-241-2/+1
|
* listener logsAdam Soltys2026-03-181-2/+1
|
* remove logsAdam Soltys2026-03-176-33/+3
|
* adjust ln minfeeregtest-dataAdam Soltys2026-03-151-6/+0
|
* pass bytes for app secretAdam Soltys2026-03-141-1/+2
|
* use bytes for nostr toolsAdam Soltys2026-03-131-0/+1
|
* fix pending payment cleanup and login password verificationAdam Soltys2026-03-061-5/+7
| | | | | | | Skip non-lightning entries in pending payments check and handle malformed password hashes gracefully during login. Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix login/reset glitchesAdam Soltys2026-03-031-7/+18
|
* fix lnurl description_hash mismatchAdam Soltys2026-02-251-2/+6
| | | | Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix addmanagersAdam Soltys2026-02-101-5/+4
|
* filter null managersAdam Soltys2026-02-101-2/+2
|
* check forwarded host headerAdam Soltys2026-02-061-1/+1
|
* health checkAdam Soltys2026-02-062-45/+13
|
* whitelist ua'sAdam Soltys2026-02-041-0/+11
|
* strip email from usernameAdam Soltys2026-02-021-0/+1
|
* consult archive for older paymentsAdam Soltys2026-01-303-6/+7
|
* check usernameAdam Soltys2026-01-291-11/+1
|
* skip captcha for torAdam Soltys2026-01-271-0/+4
|
* case insensitive usernamesAdam Soltys2026-01-261-1/+4
|
* captcha bypass whitelistAdam Soltys2026-01-261-10/+13
|
* read payments from archiveAdam Soltys2026-01-222-11/+24
|
* fix admin resetsAdam Soltys2026-01-221-7/+3
|
* api key captcha bypassAdam Soltys2026-01-221-3/+11
|
* captcha thresholdsAdam Soltys2026-01-211-11/+18
|
* require auth for accountsAdam Soltys2026-01-211-0/+5
|