diff options
| author | Adam Soltys <[email protected]> | 2023-01-13 23:50:30 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2023-01-13 23:50:30 -0800 |
| commit | 6bafea9bffe24d2227d269812a73a9a513d92ddf (patch) | |
| tree | 6dd463ffa410f4b7eebc870b24db305785414aa0 | |
| parent | 8a114c50efaac41767042766f05390aa906da157 (diff) | |
| download | coinos-server-6bafea9bffe24d2227d269812a73a9a513d92ddf.tar.gz coinos-server-6bafea9bffe24d2227d269812a73a9a513d92ddf.zip | |
internal payments
| -rw-r--r-- | lib/sendInternal.js | 242 | ||||
| -rw-r--r-- | routes/invoices.js | 4 | ||||
| -rw-r--r-- | routes/lightning/receive.js | 10 | ||||
| -rw-r--r-- | routes/payments.js | 9 | ||||
| -rw-r--r-- | routes/send.js | 1 | ||||
| -rw-r--r-- | routes/users.js | 159 |
6 files changed, 119 insertions, 306 deletions
diff --git a/lib/sendInternal.js b/lib/sendInternal.js index 5a8b0b9d..f801c9cd 100644 --- a/lib/sendInternal.js +++ b/lib/sendInternal.js @@ -6,197 +6,111 @@ import { v4 } from "uuid"; import { notify } from "$lib/notifications"; import { l, err } from "$lib/logging"; import { fail } from "$lib/utils"; +import ln from "$lib/ln"; +import { g, s, rd } from "$lib/redis"; const { HOSTNAME: hostname } = process.env; export default async ( - { amount, address, payreq, unconfidential, asset, memo, username, tip }, + { amount, address, payreq, unconfidential, memo, username, tip }, user ) => { amount = parseInt(amount); - if (!asset) asset = config.liquid.btcasset; - if (!amount || amount < 0) fail("Amount must be greater than zero"); - return db.transaction(async transaction => { - let account; - if (user.account.asset === asset && !user.account.pubkey) { - account = await db.Account.findOne({ - where: { - id: user.account.id, - pubkey: null - }, - lock: transaction.LOCK.UPDATE, - transaction - }); - } else { - account = await db.Account.findOne({ - where: { - user_id: user.id, - asset, - pubkey: null - }, - lock: transaction.LOCK.UPDATE, - transaction - }); - } - - if (!account) throw new Error("Account not found"); + let fee = 0; + let recipient; - if (account.balance < amount) { - throw new Error("Insufficient funds"); - } + let t = async () => { + await rd.watch(`user:${user_id}`); + user = await g(`user:${user_id}`); + if (user.balance < amount) throw new Error("Insufficient funds"); - let fee = 0; + user.balance -= amount; + let m = await rd.multi(); + await s(`user:${user.id}`, user); - await account.decrement({ balance: amount }, { transaction }); - await account.reload({ transaction }); + if (username && (recipient = await g(`user:${username}`))) { + let { payment_hash: h } = await ln.decode(payreq); + let invoice = payreq && (await g(`invoice:${h}`)); - let params, recipient, payment; - - if (username) { - recipient = await db.User.findOne( - { - where: { username }, - include: { - model: db.Account, - as: "account" - } - }, - { transaction } - ); - - params = { - where: { user_id: recipient.id }, - order: [["id", "DESC"]] + let t = async () => { + await rd.watch(`user:${recipient.id}`); + recipient = await g(`user:${recipient.id}`); + recipient.balance += amount; + let m = await rd.multi(); + await s(`user:${recipient.id}`, recipient); + if (!(await m.exec())) t(); }; - if (address) params.where.address = address; - else if (payreq) params.where.text = payreq; - let invoice = (address || payreq) && (await db.Invoice.findOne(params)); - - let a2; - let acc = { - user_id: recipient.id, - asset, - pubkey: null - }; - - if (recipient.account.asset === asset && !recipient.account.pubkey) - a2 = recipient.account; - else { - a2 = await db.Account.findOne({ - where: acc, - lock: transaction.LOCK.UPDATE, - transaction - }); - } - - if (a2) { - await a2.increment({ balance: amount }, { transaction }); - await a2.reload({ transaction }); - } else { - let name = asset.substr(0, 6); - let domain; - let ticker = asset.substr(0, 3).toUpperCase(); - let precision = 8; - - if (store.assets[asset]) { - ({ domain, ticker, precision, name } = store.assets[asset]); - } else { - const existing = await db.Account.findOne({ - where: { - asset - }, - order: [["id", "ASC"]], - limit: 1, - lock: transaction.LOCK.UPDATE, - transaction - }); - - if (existing) { - ({ domain, ticker, precision, name } = existing); - } - } + await t(); - acc = { ...acc, ...{ domain, ticker, precision, name } }; - acc.balance = amount; - acc.pending = 0; - acc.network = "liquid"; - a2 = await db.Account.create(acc, { transaction }); - } - - params = { - amount, - account_id: a2.id, - user_id: recipient.id, - rate: store.rates[recipient.currency], - currency: recipient.currency, - confirmed: true, - hash: `#${v4().substr(0, 6)} Payment from ${user.username}`, - memo, - network: "COINOS", - received: true, - with_id: user.id - }; + if (!(await m.exec())) t(); + } + await t(); - if (invoice) { - let { tip } = invoice; - params.invoice_id = invoice.id; - if (tip) { - params.tip = tip; - params.amount -= tip; - } + let p2 = { + id: v4(), + amount, + user_id: recipient.id, + rate: store.rates[recipient.currency], + currency: recipient.currency, + memo, + type: "internal", + with_id: user.id, + created_at: Date.now() + }; - await invoice.increment({ received: amount }, { transaction }); - await invoice.reload({ transaction }); + if (invoice) { + let { tip } = invoice; + params.invoice_id = invoice.id; + if (tip) { + params.tip = tip; + params.amount -= tip; } - let p2 = await db.Payment.create(params, { transaction }); - - p2 = p2.get({ plain: true }); - p2.account = a2.get({ plain: true }); - if (invoice) p2.invoice = invoice.get({ plain: true }); + invoice.received += amount; + await s(`invoice:${h}`, invoice); + } - emit(recipient.username, "payment", p2); - emit(recipient.username, "account", p2.account); + if (invoice) p2.invoice = invoice; + await s(`payment:${p2.id}`, p2); + await rd.lPush(`${recipient.id}:payments`, p2.id); - l("received internal", recipient.username, amount); - notify(recipient, `Received ${amount} ${a2.ticker} sats`); - callWebhook(invoice, p2); - } + emit(recipient.username, "payment", p2); - params = { - amount: -amount, - account_id: account.id, - memo, - user_id: user.id, - rate: store.rates[user.currency], - currency: user.currency, - confirmed: true, - with_id: recipient && recipient.id, - hash: `#${v4().substr(0, 6)} ${ - username ? `Payment to ${username}` : "Internal Transfer" - }`, - network: "COINOS" - }; + l("received internal", recipient.username, amount); + notify(recipient, `Received ${amount} sats`); + callWebhook(invoice, p2); + }; - if (!username) { - l("creating redeemable payment"); - params.redeemcode = v4(); - params.hash = `${hostname}/redeem/${params.redeemcode}`; - } + let p1 = { + id: v4(), + amount: -amount, + memo, + user_id: user.id, + rate: store.rates[user.currency], + currency: user.currency, + with_id: recipient && recipient.id, + hash: `#${v4().substr(0, 6)} ${ + username ? `Payment to ${username}` : "Internal Transfer" + }`, + type: "internal", + created_at: Date.now() + }; - payment = await db.Payment.create(params, { transaction }); + if (!username) { + l("creating redeemable payment"); + params.redeemcode = v4(); + params.hash = `${hostname}/redeem/${params.redeemcode}`; + } - payment = payment.get({ plain: true }); - payment.account = account.get({ plain: true }); + await s(`payment:${p1.id}`, p1); + await rd.lPush(`${user.id}:payments`, p1.id); - l("sent internal", user.username, -payment.amount); + l("sent internal", user.username, -p1.amount); - emit(user.username, "payment", payment); - emit(user.username, "account", account); + emit(user.username, "payment", p1); - return payment; - }); + return p1; }; diff --git a/routes/invoices.js b/routes/invoices.js index f7ab78cf..10a80b90 100644 --- a/routes/invoices.js +++ b/routes/invoices.js @@ -9,7 +9,6 @@ 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); }); @@ -48,7 +47,8 @@ app.post( rate, tip, user_id, - text + text, + received: 0 }; l( diff --git a/routes/lightning/receive.js b/routes/lightning/receive.js index a37dc716..ffb0ad95 100644 --- a/routes/lightning/receive.js +++ b/routes/lightning/receive.js @@ -1,3 +1,4 @@ +import { v4 } from "uuid"; import store from "$lib/store"; import { emit } from "$lib/sockets"; import config from "$config"; @@ -35,7 +36,9 @@ let handle = async () => { let amount = parseInt(received) - tip; if (amount < 0) throw new Error("amount out of range"); + let id = v4(); let payment = { + id, user_id, hash, memo, @@ -44,7 +47,8 @@ let handle = async () => { secret, rate, tip, - invoice_id: invoice.id + invoice_id: invoice.id, + created_at: Date.now() }; total = amount + tip; @@ -64,6 +68,10 @@ let handle = async () => { await t(); payment.invoice = invoice; + + await s(`payment:${id}`, payment); + await rd.lPush(`${user.id}:payments`, id); + callWebhook(invoice, payment); emit(user.username, "payment", payment); diff --git a/routes/payments.js b/routes/payments.js index 7b094ccc..b328e763 100644 --- a/routes/payments.js +++ b/routes/payments.js @@ -1,6 +1,7 @@ import app from "$app"; import config from "$config"; import store from "$lib/store"; +import { g, rd } from "$lib/redis"; import { auth, adminAuth, optionalAuth } from "$lib/passport"; import fs from "fs"; @@ -22,7 +23,7 @@ app.post("/lightning/send", auth, lnRoutes.send); app.get( "/payments", auth, - async ({ uuid, query: { start, end, limit, offset } }, res) => { + async ({ user: { id }, query: { start, end, limit, offset } }, res) => { if (limit) limit = parseInt(limit); if (offset) offset = parseInt(offset); @@ -30,8 +31,10 @@ app.get( // if (start) where.createdAt[Op.gte] = new Date(parseInt(start)); // if (end) where.createdAt[Op.lte] = new Date(parseInt(end)); - let payments = await redis.get(`${uuid}:payments`); - res.send({ payments, total }); + let payments = (await rd.lRange(`${id}:payments`, 0, -1)) || []; + payments = await Promise.all(payments.map(id => g(`payment:${id}`))); + payments = payments.filter(p => p); + res.send({ payments, total: payments.length }); } ); diff --git a/routes/send.js b/routes/send.js index 591b5ab7..3f71a496 100644 --- a/routes/send.js +++ b/routes/send.js @@ -8,6 +8,7 @@ export default async (req, res, next) => { let payment = await sendInternal(req.body, req.user); res.send(payment); } catch (e) { + console.log(e) err("problem sending internal payment", e.message); return res.code(500).send(e.message); } diff --git a/routes/users.js b/routes/users.js index 97ff9dd4..c0324803 100644 --- a/routes/users.js +++ b/routes/users.js @@ -84,72 +84,53 @@ app.post("/register", async (req, res) => { } }); -app.post("/disable2fa", auth, async (req, res) => { - try { - let { - user, - body: { token } - } = req; - - if (user.twofa && !authenticator.check(token, user.otpsecret)) { - return res.code(401).send("2fa required"); - } - - user.twofa = false; - await user.save(); - emit(user.username, "user", user); - emit(user.username, "otpsecret", user.otpsecret); - l("disabled 2fa", user.username); - res.send({}); - } catch (e) { - res.code(500).send("Problem disabling 2fa"); +app.post("/disable2fa", auth, async ({ user, body: { token } }, res) => { + let { id, twofa, username, otpsecret } = user; + if (twofa && !authenticator.check(token, otpsecret)) { + return res.code(401).send("2fa required"); } + + user.twofa = false; + await s(`user:${id}`, user); + emit(username, "user", user); + emit(username, "otpsecret", user.otpsecret); + l("disabled 2fa", username); + res.send({}); }); -app.post("/2fa", auth, async (req, res) => { - let { user } = req; - try { - const isValid = authenticator.check(req.body.token, req.user.otpsecret); - if (isValid) { - user.twofa = true; - user.save(); - emit(user.username, "user", req.user); - } else { - return res.code(500).send("Invalid token"); - } - } catch (e) { - err("error setting up 2fa", e); +app.post("/2fa", auth, async ({ user, body: { token } }, res) => { + let { id, otpsecret, username } = user; + const isValid = authenticator.check(token, otpsecret); + if (isValid) { + user.twofa = true; + await s(`user:${id}`, user); + emit(username, "user", user); + } else { + return res.code(500).send("Invalid token"); } - l("enabled 2fa", user.username); + l("enabled 2fa", username); res.send({}); }); -app.post("/user", auth, async (req, res) => { +app.post("/user", auth, async ({ user, body: { confirm, password, pin, newpin, username }}, res) => { try { let { user } = req; l("updating user", user.username); - let { confirm, password, pin, newpin, username } = req.body; - if (user.pin && !(pin === user.pin)) throw new Error("Pin required"); if (typeof newpin !== "undefined") user.pin = newpin; if (!user.pin) user.pin = null; let exists; - - if (username) - exists = await db.User.findOne({ - where: { username } - }); + if (username) exists = await g(`user:${username}`); let token; if (user.username !== username && exists) { err("username taken", username, user.username, exists.username); throw new Error("Username taken"); } else if (username) { - store.sockets[username] = store.sockets[user.username]; if (user.username !== username) l("changing username", user.username, username); user.username = username; @@ -195,99 +176,6 @@ app.post("/user", auth, async (req, res) => { } }); -app.post("/keys", auth, async (req, res) => { - const { key: hex } = req.body; - const key = await db.Key.create({ - user_id: req.user.id, - hex - }); - emit(req.user.username, "key", key); - res.send(key); -}); - -app.post("/keys/delete", auth, async (req, res) => { - const { hex } = req.body; - ( - await db.Key.findOne({ - where: { - user_id: req.user.id, - hex - } - }) - ).destroy(); -}); - -app.post("/updateSeeds", auth, async (req, res) => { - let { user } = req; - let { seeds } = req.body; - let keys = Object.keys(seeds); - - for (let i = 0; i < keys.length; i++) { - let id = keys[i]; - let seed = seeds[id]; - await db.Account.update( - { seed }, - { - where: { id, user_id: user.id } - } - ); - - const account = await db.Account.findOne({ - where: { id } - }); - - emit(user.username, "account", account); - } - - res.send({}); -}); - -app.post("/accounts/delete", auth, async (req, res) => { - const { id } = req.body; - const account = await db.Account.findOne({ where: { id } }); - if (account) await account.destroy(); - res.send({}); -}); - -app.post("/accounts", auth, async (req, res) => { - const { - name, - seed, - pubkey, - ticker, - precision, - path, - privkey, - network - } = req.body; - const { user } = req; - - let account = await db.Account.create({ - user_id: user.id, - asset: config.liquid.btcasset, - balance: 0, - pending: 0, - name, - ticker, - precision, - pubkey, - privkey, - seed, - path, - network - }); - - emit(user.username, "account", account); - - if (pubkey) user.index++; - user.account_id = account.id; - await user.save(); - user.account = account; - emit(user.username, "user", user); - - res.send(account); -}); - let login = async (req, res) => { try { const { params, sig, key, password, username, token: twofa } = req.body; @@ -337,7 +225,6 @@ let login = async (req, res) => { let token = jwt.sign(payload, config.jwt); res.cookie("token", token, { expires: new Date(Date.now() + 432000000) }); user = pick(user, whitelist); - console.log("SENDING", user, token) res.send({ user, token }); } catch (e) { console.log(e); |
