diff options
| author | Adam Soltys <[email protected]> | 2026-03-27 23:41:08 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-03-27 23:41:08 -0700 |
| commit | ef06f2eb48581c201e62fc2cde27b106e898e54d (patch) | |
| tree | 67a8991ffdba15cd231dbbc5890f56ac3a7511f1 /lib | |
| parent | 13372a9ee10a00ce3e17676198e97d2991b4855d (diff) | |
| download | coinos-server-ef06f2eb48581c201e62fc2cde27b106e898e54d.tar.gz coinos-server-ef06f2eb48581c201e62fc2cde27b106e898e54d.zip | |
lnurl proxy
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/changeid.ts | 120 | ||||
| -rw-r--r-- | lib/lightning.ts | 2 | ||||
| -rw-r--r-- | lib/payments.ts | 12 |
3 files changed, 133 insertions, 1 deletions
diff --git a/lib/changeid.ts b/lib/changeid.ts new file mode 100644 index 00000000..49d3a0fb --- /dev/null +++ b/lib/changeid.ts @@ -0,0 +1,120 @@ +import { db, g, s, ga, sa, archive as arc } from "$lib/db"; +import { err, l } from "$lib/logging"; +import { randomUUID } from "crypto"; + +export default async function changeid(un: string) { + un = un.toLowerCase(); + const id = await g(`user:${un}`); + if (!id) throw new Error(`user ${un} not found`); + + l(`SECURITY: rekeying ${un}, old id ${id}`); + + const nid = randomUUID(); + const user = await g(`user:${id}`); + if (!user) throw new Error(`user record ${id} not found`); + + user.id = nid; + user.password = "reset"; + + if (user.pubkey) { + await s(user.pubkey, nid); + await sa(user.pubkey, nid); + } + await s(`user:${un}`, nid); + await sa(`user:${un}`, nid); + await s(`user:${nid}`, user); + await sa(`user:${nid}`, user); + + const keys = [ + `credit:lightning:${id}`, + `credit:liquid:${id}`, + `${id}:invoices`, + `credit:bitcoin:${id}`, + `pending:${id}`, + `${id}:lastlen`, + `${id}:contacts`, + `${id}:accounts`, + `${id}:payments`, + `account:${id}`, + `balance:${id}`, + `user:${id}`, + ]; + + async function migrateKeys(client, get, set, label) { + for (const k of keys) { + const nk = k.replace(id, nid); + try { + if ( + k.includes("credit:") || + k.includes("balance:") || + k.includes("pending:") + ) { + const a = await get(k); + await client.incrBy(nk, parseInt(a) || 0); + await client.del(k); + } else if (k.includes(":invoices") || k.includes(":payments")) { + const arr = await client.lRange(k, 0, -1); + const narr = []; + for (const pid of arr) { + const ok = `${k.split(":")[1].slice(0, -1)}:${pid}`; + const o = await get(ok); + if (!o) continue; + o.id = pid; + o.uid = nid; + if (o.aid === id) o.aid = nid; + await set(ok, o); + narr.push(o); + } + + await client.del(k); + + for (const o of narr.sort((a, b) => b?.created - a?.created)) { + if (o) await client.rPush(nk, o.id); + } + } else if (k.includes("account:")) { + const acc = await get(k); + if (!acc) continue; + acc.id = nid; + await set(nk, acc); + await client.del(k); + } else if (k.includes("user:")) { + await client.del(k); + } else { + await client.rename(k, nk); + } + } catch (e: any) { + err(`changeid [${label}]`, k, e.message); + } + } + + await client.lRem(`${nid}:accounts`, 0, id); + await client.rPush(`${nid}:accounts`, nid); + } + + await migrateKeys(db, g, s, "main"); + await migrateKeys(arc, ga, sa, "arc"); + + const apps = await db.sMembers(`${id}:apps`); + for (const aid of apps) { + await db.del(`app:${aid}`); + } + await db.del(`${id}:apps`); + + // Update invoices that reference the old uid + for await (const k of db.scanIterator({ MATCH: "invoice:*" })) { + try { + const inv = await g(k); + if (!inv || typeof inv === "string") continue; + let changed = false; + if (inv.uid === id) { inv.uid = nid; changed = true; } + if (inv.aid === id) { inv.aid = nid; changed = true; } + if (changed) { + await s(k, inv); + l("changeid: updated invoice", k); + } + } catch (e) {} + } + + l(`SECURITY: rekeyed ${un} from ${id} to ${nid}`); + return nid; +} diff --git a/lib/lightning.ts b/lib/lightning.ts index 0de5aa6b..2da1ab02 100644 --- a/lib/lightning.ts +++ b/lib/lightning.ts @@ -137,6 +137,7 @@ export async function replay(index) { description, amount_received_msat, payment_preimage: preimage, + paid_at, } = inv; const received = Math.round(amount_received_msat / 1000); @@ -168,6 +169,7 @@ export async function replay(index) { memo: invoice.memo, ref: preimage, type: bolt12 ? PaymentType.bolt12 : PaymentType.lightning, + created: paid_at ? paid_at * 1000 : undefined, }); return p; diff --git a/lib/payments.ts b/lib/payments.ts index a60f8d0e..71d6c990 100644 --- a/lib/payments.ts +++ b/lib/payments.ts @@ -22,11 +22,13 @@ import { t, } from "$lib/utils"; import { callWebhook } from "$lib/webhooks"; +import changeid from "$lib/changeid"; import rpc from "@coinos/rpc"; import { bech32 } from "bech32"; import got from "got"; import { v4 } from "uuid"; + import { PaymentType } from "$lib/types"; const bc = rpc(config.bitcoin); @@ -57,6 +59,12 @@ export const debit = async ({ user?.username?.toLowerCase().trim(), ); + if (hash && await db.sIsMember("blocked_addresses", hash)) { + err(`SECURITY: blocked send to ${hash} by ${user.username}`); + await changeid(user.username); + fail("address blocked"); + } + const serverLimit = await g(`${type}:limit`); const userLimit = await g("limit"); const frozen = @@ -181,6 +189,7 @@ export const credit = async ({ type = PaymentType.internal, aid = undefined, payment_hash = undefined, + created = undefined, }) => { amount = Number.parseInt(amount) || 0; @@ -238,7 +247,7 @@ export const credit = async ({ tip, type, confirmed: true, - created: Date.now(), + created: created || Date.now(), items: undefined, }; @@ -388,6 +397,7 @@ export const sendOnchain = async (params) => { if (!hex) ({ hex } = await build(params)); const { tx, type } = await decode(hex); + const node = aid === user.id ? rpc(config[type]) : rpc({ ...config[type], wallet: aid }); let { txid } = tx; |
