diff options
| -rw-r--r-- | docker-compose.yml | 13 | ||||
| -rw-r--r-- | index.js | 30 | ||||
| -rw-r--r-- | lib/nostr.js | 1 | ||||
| -rw-r--r-- | lib/passport.js | 5 | ||||
| -rw-r--r-- | lib/register.js | 15 | ||||
| -rw-r--r-- | lib/sockets.js | 26 | ||||
| -rw-r--r-- | lib/utils.js | 21 | ||||
| -rw-r--r-- | lib/whitelist.js | 1 | ||||
| -rw-r--r-- | package.json | 2 | ||||
| -rw-r--r-- | routes/invoices.js | 49 | ||||
| -rw-r--r-- | routes/lightning/receive.js | 161 | ||||
| -rw-r--r-- | routes/users.js | 26 |
12 files changed, 135 insertions, 215 deletions
diff --git a/docker-compose.yml b/docker-compose.yml index 95516fe0..83214b92 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,6 @@ services: - rd - bc - cl - build: . command: "pnpm dev" environment: NODE_OPTIONS: "--es-module-specifier-resolution=node --experimental-modules --loader ./loader.js" @@ -32,8 +31,16 @@ services: volumes: - ./data/lightning:/root/.lightning restart: always + clb: + image: elementsproject/lightningd:latest + container_name: clb + environment: + LIGHTNINGD_NETWORK: regtest + volumes: + - ./data/lightningb:/root/.lightning + restart: always rd: - image: redis + image: eqalpha/keydb container_name: rd volumes: - ./data/redis:/data @@ -45,6 +52,6 @@ services: user: 100:100 volumes: - "./data/nostr/data:/usr/src/app/db" - - "./data/nostr/data.toml:/usr/src/app/config.toml" + - "./data/nostr/config.toml:/usr/src/app/config.toml" ports: - '8080:8080' @@ -1,20 +1,20 @@ -import app from "./app"; -import { l, err } from "./lib/logging"; +import app from "./app.js"; +import { l, err } from "$lib/logging"; -import "./lib/redis"; -import "./lib/sockets"; -import "./lib/passport"; -import "./lib/upload"; -import "./lib/rates"; -import "./lib/notifications"; -import "./lib/mail"; -import "./lib/nostr"; +import "$lib/redis"; +import "$lib/sockets"; +import "$lib/passport"; +import "$lib/upload"; +import "$lib/rates"; +import "$lib/notifications"; +import "$lib/mail"; +import "$lib/nostr"; -import "./routes/locations"; -import "./routes/invoices"; -import "./routes/payments"; -import "./routes/users"; -import "./routes/requests"; +import "$routes/locations"; +import "$routes/invoices"; +import "$routes/payments"; +import "$routes/users"; +import "$routes/requests"; let host = process.env.HOST || "0.0.0.0"; let port = process.env.PORT || 3119; diff --git a/lib/nostr.js b/lib/nostr.js index ad089bb5..08ed8b61 100644 --- a/lib/nostr.js +++ b/lib/nostr.js @@ -142,7 +142,6 @@ pool.on("event", async (relay, sub, ev) => { }; await redis.set(`user:${pubkey}`, JSON.stringify(user)); - await redis.set(`user:${user.username}`, JSON.stringify(user)); coinos.send(ev); } else if (sub.includes("follows")) { let pubkey = sub.split(":")[0]; diff --git a/lib/passport.js b/lib/passport.js index b559b2f9..25dc0103 100644 --- a/lib/passport.js +++ b/lib/passport.js @@ -34,8 +34,9 @@ fastifyPassport.use( }, async (payload, next) => { try { - let { uuid } = payload; - let user = await g(`user:${uuid}`); + let { id } = payload; + let user = await g(`user:${id}`); + user.haspin = !!user.pin; next(null, user); } catch (e) { console.log("payload", payload); diff --git a/lib/register.js b/lib/register.js index 9f18c15e..8ac5355f 100644 --- a/lib/register.js +++ b/lib/register.js @@ -14,8 +14,8 @@ export default async (user, ip, requireChallenge) => { if (!username) throw new Error("Username required"); username = username.replace(/ /g, ""); - let uuid = v4(); - user.uuid = uuid; + let id = v4(); + user.id = id; let exists = await g(`user:${username}`); if (exists) throw new Error(`Username ${username} taken`); @@ -24,8 +24,6 @@ export default async (user, ip, requireChallenge) => { user.password = await bcrypt.hash(password, 1); } - console.log("BOO"); - if (!config.ipxapi || ip.startsWith("127") || ip.startsWith("192")) { user.currency = "CAD"; } else { @@ -53,14 +51,9 @@ export default async (user, ip, requireChallenge) => { user.ip = ip; } - console.log("user uuid", user.uuid, user.username); + await s(`user:${id}`, user); + await s(`user:${user.username}`, id); - try { - await s(`user:${user.uuid}`, user); - await s(`user:${user.username}`, user); - } catch (e) { - console.log("HUH", e); - } l("new user", user.username); return user; }; diff --git a/lib/sockets.js b/lib/sockets.js index 0f343937..c94c29a0 100644 --- a/lib/sockets.js +++ b/lib/sockets.js @@ -8,7 +8,7 @@ import { differenceInSeconds, subSeconds } from "date-fns"; import fastifyWs from "@fastify/websocket"; import whitelist from "./whitelist"; import { l, err, warn } from "$lib/logging"; -import { getUser } from "$lib/utils"; +import { g } from "$lib/redis"; const code = 1000; @@ -92,24 +92,24 @@ app.register(async function(app) { if (!token || token === "null" || !jwt.decode(token)) return ws.close(code, `bad token ${token}`); - let { uuid } = jwt.decode(token); - if (!uuid) throw new Error("Invalid JWT token"); - console.log("UUID", uuid) - let user = await getUser(uuid); + let { id: user_id } = jwt.decode(token); + if (!user_id) throw new Error("Invalid JWT token"); + let user = await g(`user:${user_id}`); if (!user) throw new Error("User not found"); + user.haspin = !!user.pin; - if (!store.sockets[uuid]) store.sockets[uuid] = {}; - const existing = Object.keys(store.sockets[uuid]); + if (!store.sockets[user_id]) store.sockets[user_id] = {}; + const existing = Object.keys(store.sockets[user_id]); if (existing.length > 4) { - const p = existing.find(sid => sid !== id); - store.sockets[uuid][p].send(JSON.stringify({ type: "logout" })); + const p = existing.find(sid => sid !== user_id); + store.sockets[user_id][p].send(JSON.stringify({ type: "logout" })); setTimeout(() => { - if (store.sockets[uuid][p]) - store.sockets[uuid][p].close(code, "too many sockets"); + if (store.sockets[user_id][p]) + store.sockets[user_id][p].close(code, "too many sockets"); }, 5000); } - store.sockets[uuid][id] = ws; - names[id] = uuid; + store.sockets[user_id][id] = ws; + names[id] = user_id; ws.user = user; ws.send( diff --git a/lib/utils.js b/lib/utils.js index 559f8531..b556c46a 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,7 +1,6 @@ import store from "$lib/store"; import { g } from "$lib/redis"; import config from "$config"; -import ln from "$lib/ln"; export const nada = () => {}; @@ -18,15 +17,6 @@ export const requirePin = async ({ body, user }) => { throw new Error("Invalid pin"); }; -export const getUser = async (uuid) => { - let user = g(`user:${uuid}`); - if (!user) throw new Error("user not found"); - - user.haspin = !!user.pin; - - return user; -}; - export const prod = process.env.NODE_ENV === "production"; export const fail = msg => { throw new Error(msg); @@ -35,14 +25,5 @@ export const fail = msg => { export const SATS = 100000000; export const toSats = n => Math.round(n * SATS); -export const derivePayRequest = async ({ amount, memo, tip }) => { - if (!tip) tip = 0; - let value = amount + tip; - - if (!memo) memo = "coinos"; - return ( - await ln.invoice(value ? `${value}sat` : "any", new Date(), memo, 360) - ).bolt11; -}; - export const uniq = (a, k) => [...new Map(a.map(x => [k(x), x])).values()]; +export const pick = (O, K) => K.reduce((o, k) => ((o[k] = O[k]), o), {}); diff --git a/lib/whitelist.js b/lib/whitelist.js index 523e9d37..49cc9af3 100644 --- a/lib/whitelist.js +++ b/lib/whitelist.js @@ -25,7 +25,6 @@ export default [ "symbol", "theme", "twofa", - "uuid", "unit", "username", "verified" diff --git a/package.json b/package.json index 408df40e..2c1b7258 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "private": true, "scripts": { - "start": "bun index.js", + "start": "node index.js", "dev": "nodemon index.js" }, "dependencies": { diff --git a/routes/invoices.js b/routes/invoices.js index ddd840ec..f7ab78cf 100644 --- a/routes/invoices.js +++ b/routes/invoices.js @@ -1,76 +1,67 @@ import app from "$app"; import store from "$lib/store"; import { optionalAuth } from "$lib/passport"; -import { err, l } from "$lib/logging"; -import { SATS, derivePayRequest } from "$lib/utils"; +import { l } from "$lib/logging"; import { emit } from "$lib/sockets"; -import { v4 } from "uuid"; import { rd, g, s } from "$lib/redis"; +import ln from "$lib/ln"; app.get("/invoice", async ({ query: { id } }, res) => { let invoice = await g(`invoice:${id}`); invoice.user = await g(`user:${invoice.user_id}`); + console.log("INV", invoice) res.send(invoice); }); app.post( "/invoice", optionalAuth, - async ( - { - body: { type = "bech32", liquidAddress, id, invoice, user, tx }, - user: me - }, - res - ) => { - let { - blindkey, - currency, - tip, - amount, - rate, - network, - request_id - } = invoice; - let text; + async ({ body: { invoice, user }, user: me }, res) => { + let { currency, tip, amount, rate, request_id } = invoice; if (amount < 0) throw new Error("amount out of range"); if (tip > 5 * amount || tip < 0) throw new Error("tip amount out of range"); tip = tip || 0; if (!user) user = me; - if (!user) throw new Error("user not provided"); + let user_id = await g(`user:${user.username}`); + user = await g(`user:${user_id}`); - user = await g(`user:${user.id}`); + if (!user) throw new Error("user not provided"); if (!currency) currency = user.currency; if (!rate) rate = store.rates[currency]; if (amount < 0) throw new Error("invalid amount"); + let { payment_hash: id, bolt11: text } = await ln.invoice( + amount ? `${amount + tip}sat` : "any", + new Date(), + "", + 3600 + ); + invoice = { ...invoice, - id: v4(), + id, amount, currency, rate, tip, - unconfidential, - user_id: user.id + user_id, + text }; - invoice.text = await derivePayRequest(invoice); - l( "creating invoice", user.username, - network, amount, tip, currency, `${text.substr(0, 8)}..${text.substr(-6)}` ); - await rd.lPush(`${id}:invoices`, invoice.id); + await s(`invoice:${id}`, invoice); + await rd.lPush(`${user.id}:invoices`, id); if (request_id) { let request = await g(`request:${request_id}`); diff --git a/routes/lightning/receive.js b/routes/lightning/receive.js index a778271b..a37dc716 100644 --- a/routes/lightning/receive.js +++ b/routes/lightning/receive.js @@ -6,132 +6,79 @@ import { callWebhook } from "$lib/webhooks"; import { computeConversionFee } from "./conversionFee"; import ln from "$lib/ln"; import { l, err, warn } from "$lib/logging"; +import { g, s, rd } from "$lib/redis"; -const handlePayment = async msg => { - try { - l("incoming lightning payment", msg.received); - - if (!msg.secret) return; - let account, total, user; - - const invoice = await db.Invoice.findOne({ - where: { - text: msg.request - } - }); +let handle = async () => { + let { + payment_hash, + bolt11: request, + pay_index, + status, + msatoshi_received, + payment_preimage: secret + } = await ln.waitanyinvoice(await g("pay_index")); - if (!invoice) - return warn("received lightning with no invoice", msg.request); + await s("pay_index", pay_index); - await db.transaction(async transaction => { - const { text: hash, currency, memo, rate, tip, user_id } = invoice; - const amount = parseInt(msg.received) - tip; - if (amount < 0) throw new Error("amount out of range"); + let received = Math.round(msatoshi_received / 1000); - account = await db.Account.findOne({ - where: { - user_id, - asset: config.liquid.btcasset, - pubkey: null - }, - include: { - model: db.User, - as: "user" - }, - lock: transaction.LOCK.UPDATE, - transaction - }); + try { + l("incoming lightning payment", received); - ({ user } = account); + if (!secret) return; + let account, total, user; - let preimage = msg.secret; + let invoice = await g(`invoice:${payment_hash}`); + if (!invoice) return warn("received lightning with no invoice", request); - let payment = await db.Payment.create( - { - account_id: account.id, - user_id, - hash, - memo, - amount, - currency, - preimage, - rate, - received: true, - confirmed: true, - network: "lightning", - tip, - invoice_id: invoice.id - }, - { transaction } - ); + let { text: hash, currency, memo, rate, tip, user_id } = invoice; + let amount = parseInt(received) - tip; + if (amount < 0) throw new Error("amount out of range"); - total = amount + tip; + let payment = { + user_id, + hash, + memo, + amount, + currency, + secret, + rate, + tip, + invoice_id: invoice.id + }; - invoice.status = "paid"; - await invoice.save({ transaction }); + total = amount + tip; - await invoice.increment({ received: total }, { transaction }); - await account.increment({ balance: total }, { transaction }); - // get the # of fee credits you would need to pay off this amount of bitcoin - await account.increment( - { lightning_credits: computeConversionFee(total) }, - { transaction } - ); - await account.reload({ transaction }); - await invoice.reload({ transaction }); - await payment.save({ transaction }); - store.payments.push(msg.request); + invoice.status = "paid"; + invoice.received += total; + await s(`invoice:${payment_hash}`, invoice); - payment = payment.get({ plain: true }); - payment.account = account.get({ plain: true }); - payment.invoice = invoice.get({ plain: true }); + let t = async () => { + await rd.watch(`user:${user_id}`); + user = await g(`user:${user_id}`); + user.balance += total; + let m = await rd.multi(); + await s(`user:${user.id}`, user); + if (!(await m.exec())) t(); + }; + await t(); - callWebhook(invoice, payment); + payment.invoice = invoice; + callWebhook(invoice, payment); - emit(user.username, "payment", payment); - emit(user.username, "account", payment.account); - notify(user, `Received ${total} SAT`); + emit(user.username, "payment", payment); + notify(user, `Received ${total} SAT`); - l( - "lightning payment received", - user.username, - payment.amount, - payment.tip - ); - }); + l("lightning payment received", user.username, payment.amount, payment.tip); } catch (e) { console.log(e); err("problem receiving lightning payment", e.message); } }; -const poll = async ln => { - try { - const wait = async i => { - const { - bolt11: request, - pay_index, - status, - msatoshi_received, - payment_preimage: secret - } = await ln.waitanyinvoice(i); - - let settled = status === "paid"; - let received = parseInt(msatoshi_received / 1000); - - await handlePayment({ - request, - received, - secret - }); - await wait(pay_index); - }; - - const { invoices } = await ln.listinvoices(); - await wait(Math.max(...invoices.map(i => i.pay_index).filter(n => n))); - } catch (e) { - err("problem waiting for c-lightning invoice", e.message); - } +let listen = async () => { + await handle(); + listen(); }; -poll(ln); +listen(); diff --git a/routes/users.js b/routes/users.js index 8f0b9106..97ff9dd4 100644 --- a/routes/users.js +++ b/routes/users.js @@ -3,7 +3,7 @@ import { g, s } from "$lib/redis"; import config from "$config"; import store from "$lib/store"; import { auth, optionalAuth } from "$lib/passport"; -import { getUser, nada, uniq, wait } from "$lib/utils"; +import { nada, pick, uniq, wait } from "$lib/utils"; import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { authenticator } from "otplib"; @@ -14,11 +14,9 @@ import register from "$lib/register"; import { requirePin } from "$lib/utils"; import got from "got"; -const pick = (O, ...K) => K.reduce((o, k) => ((o[k] = O[k]), o), {}); - app.get("/me", auth, async (req, res) => { try { - res.send(pick(req.user, ...whitelist)); + res.send(pick(req.user, whitelist)); } catch (e) { console.log("problem fetching user", e); res.code(500).send(e.message); @@ -32,6 +30,9 @@ app.get("/users/:key", async ({ params: { key } }, res) => { } let user = await g(`user:${key}`); + if (typeof user === "string") { + user = await g(`user:${user}`); + } if (!user && key.length === 64) { user = { @@ -41,7 +42,6 @@ app.get("/users/:key", async ({ params: { key } }, res) => { }; } - console.log(user) if (!user) return res.code(500).send("User not found"); let whitelist = [ @@ -53,10 +53,10 @@ app.get("/users/:key", async ({ params: { key } }, res) => { "pubkey", "display", "prompt", - "uuid" + "id" ]; - res.send(pick(user, ...whitelist)); + res.send(pick(user, whitelist)); } catch (e) { console.log(e); res.code(500).send(e.message); @@ -78,7 +78,7 @@ app.post("/register", async (req, res) => { user = await register(user, ip, false); l("registered new user", username); - res.send(pick(user, ...whitelist)); + res.send(pick(user, whitelist)); } catch (e) { res.code(500).send(e.message); } @@ -308,7 +308,8 @@ let login = async (req, res) => { return; } - let user = await getUser(username); + let id = await g(`user:${username}`); + let user = await g(`user:${id}`); if ( !user || @@ -332,10 +333,11 @@ let login = async (req, res) => { req.headers["x-forwarded-for"] || req.socket.remoteAddress ); - let payload = { username, uuid: user.uuid }; + let payload = { username, id: user.id }; let token = jwt.sign(payload, config.jwt); res.cookie("token", token, { expires: new Date(Date.now() + 432000000) }); - user = pick(user, ...whitelist); + user = pick(user, whitelist); + console.log("SENDING", user, token) res.send({ user, token }); } catch (e) { console.log(e); @@ -413,7 +415,7 @@ app.post("/otpsecret", auth, async function(req, res) { }); app.get("/contacts", auth, async function({ user: { uuid } }, res) { - let payments = await g(`${uuid}:payments`) || []; + let payments = (await g(`${uuid}:payments`)) || []; res.send( Promise.all( [...new Set(payments.map(p => p.with_id))].map(id => g(`user:${id}`)) |
