diff options
| author | Adam Soltys <[email protected]> | 2019-09-15 23:05:26 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2019-09-15 23:05:26 -0700 |
| commit | 765c6a42135b9334b616c26b22c816edd06e382a (patch) | |
| tree | 05da4f343358fb5e9a9083e348783f0fab973dd8 /index.js | |
| parent | 087fdd27f3683a1deb9376b7a72aa608e6fd8e6a (diff) | |
| download | coinos-server-765c6a42135b9334b616c26b22c816edd06e382a.tar.gz coinos-server-765c6a42135b9334b616c26b22c816edd06e382a.zip | |
refactor
Diffstat (limited to 'index.js')
| -rw-r--r-- | index.js | 745 |
1 files changed, 8 insertions, 737 deletions
@@ -1,39 +1,19 @@ -const { Client } = require("authy-client"); const axios = require("axios"); -const bcrypt = require("bcrypt"); const bodyParser = require("body-parser"); -const bolt11 = require("bolt11"); const compression = require("compression"); const cookieParser = require("cookie-parser"); const BitcoinCore = require("bitcoin-core"); const cors = require("cors"); const express = require("express"); -const fb = require("fb"); const graphqlHTTP = require("express-graphql"); -const jwt = require("jsonwebtoken"); const morgan = require("morgan"); -const mailgun = require("mailgun-js"); -const uuidv4 = require("uuid/v4"); -const reverse = require("buffer-reverse"); -const Sequelize = require("sequelize"); -const authyVerify = require("./authy"); const config = require("./config"); -const whitelist = require("./whitelist"); -const createLnrpc = require("./lib/createLnrpc"); -const bitcoin = require("bitcoinjs-lib"); const l = console.log; -/* eslint-disable-next-line */ -const pick = (O, ...K) => K.reduce((o, k) => ((o[k] = O[k]), o), {}); -const authy = new Client({ key: config.authy.key }); - (async () => { - const lna = await createLnrpc(config.lna); - const lnb = await createLnrpc(config.lnb); const bc = new BitcoinCore(config.bitcoin); - const app = express(); app.enable("trust proxy"); app.use(bodyParser.urlencoded({ extended: true })); @@ -44,11 +24,10 @@ const authy = new Client({ key: config.authy.key }); app.use(morgan("combined")); const server = require("http").Server(app); - const db = await require("./db")(lna); + const db = await require("./db"); const [socket, emit] = require("./sockets")(app, db, server); const passport = require("./passport")(db); const auth = passport.authenticate("jwt", { session: false }); - const seen = []; app.use(passport.initialize()); @@ -61,36 +40,10 @@ const authy = new Client({ key: config.authy.key }); }) ); - const handlePayment = async msg => { - if (!msg.settled) return; - - let payment = await db.Payment.findOne({ - include: { model: db.User, as: "user" }, - where: { - hash: msg.payment_request - } - }); - - if (!payment) return; - - payment.received = true; - payment.user.balance += parseInt(msg.value); - payment.rate = app.get("rates").ask; - - await payment.save(); - await payment.user.save(); - payments.push(msg.payment_request); - - emit(payment.user.username, "invoice", msg); - emit(payment.user.username, "user", payment.user); - }; - - const invoices = lna.subscribeInvoices({}); - invoices.on("data", handlePayment); - - const invoicesb = lnb.subscribeInvoices({}); - invoicesb.on("data", handlePayment); + require("./binance")(app); + require("./kraken")(app, socket); + const seen = []; const addresses = {}; await db.User.findAll({ attributes: ["username", "address"] @@ -102,697 +55,15 @@ const authy = new Client({ key: config.authy.key }); attributes: ["hash"] })).map(p => p.hash); - require("./zmq")(app, bc, db, addresses, payments, emit); - - app.post("/register", async (req, res) => { - let err = m => res.status(500).send(m); - let user = req.body; - if (!user.username) return err("Username required"); - if (user.password.length < 2) return err("Password too short"); - - let exists = await db.User.count({ where: { username: user.username } }); - if (exists) return err("Username taken"); - - user.address = (await lna.newAddress({ type: 1 }, lna.meta)).address; - user.password = await bcrypt.hash(user.password, 1); - user.name = user.username; - addresses[user.address] = user.username; - - await db.User.create(user); - res.send(pick(user, ...whitelist)); - }); - - const requestEmail = async user => { - user.emailToken = uuidv4(); - await user.save(); - - let mg = mailgun(config.mailgun); - let msg = { - from: "CoinOS <[email protected]>", - to: user.email, - subject: "CoinOS Email Verification", - html: `Visit <a href="https://coinos.io/verifyEmail/${user.username}/${user.emailToken}">https://coinos.io/verify/${user.username}/${user.emailToken}</a> to verify your email address.` - }; - - try { - mg.messages().send(msg); - } catch (e) { - l(e); - } - }; - - const requestPhone = async user => { - user.phoneToken = Math.floor(100000 + Math.random() * 900000); - await user.save(); - const client = require("twilio")( - config.twilio.sid, - config.twilio.authToken - ); - - await client.messages.create({ - body: user.phoneToken, - from: config.twilio.number, - to: user.phone - }); - }; - - app.post("/requestEmail", auth, async (req, res) => { - req.user.email = req.body.email; - await requestEmail(req.user); - res.end(); - }); - - app.post("/requestPhone", auth, async (req, res) => { - req.user.phone = req.body.phone; - await requestPhone(req.user); - res.end(); - }); - - app.post("/user", auth, async (req, res) => { - let { user } = req; - let { - email, - phone, - twofa, - pin, - pinconfirm, - password, - passconfirm - } = req.body; - - if (user.email !== email && require("email-validator").validate(email)) { - user.email = email; - user.emailVerified = false; - requestEmail(user); - } - - if (user.phone !== phone) { - user.phone = phone; - user.phoneVerified = false; - requestPhone(user); - } - - user.email = email; - user.phone = phone; - user.twofa = twofa; - - if (password && password === passconfirm) { - user.password = await bcrypt.hash(password, 1); - } - - if (pin && pin === pinconfirm) user.pin = await bcrypt.hash(pin, 1); - - if (twofa && !user.authyId && user.phoneVerified) { - try { - let r = await authy.registerUser({ countryCode: "CA", email, phone }); - user.authyId = r.user.id; - } catch (e) { - l(e); - } - } - - await user.save(); - emit(req.user.username, "user", req.user); - res.send(user); - }); - - app.post("/forgot", async (req, res) => { - let { user } = req; - let mg = mailgun(config.mailgun); - let msg = { - from: "CoinOS <[email protected]>", - to: user.email, - subject: "CoinOS Password Reset", - html: `Visit <a href="https://coinos.io/reset/${user.username}/${user.token}">https://coinos.io/reset/${user.username}/${user.token}</a> to reset your password.` - }; - - try { - mg.messages().send(msg); - } catch (e) { - l(e); - } - }); - - app.get("/verifyEmail/:username/:token", auth, async (req, res) => { - let user = await db.User.findOne({ - where: { - username: req.params.username, - emailToken: req.params.token - } - }); - - if (user) { - user.emailVerified = true; - await user.save(); - - emit(user.username, "user", user); - emit(user.username, "emailVerified", true); - - res.end(); - } else { - res.status(500).send("invalid token or username"); - } - }); - - app.get("/verifyPhone/:username/:token", auth, async (req, res) => { - let user = await db.User.findOne({ - where: { - username: req.params.username, - phoneToken: req.params.token - } - }); - - console.log(user, req.params); - - if (user) { - user.phoneVerified = true; - await user.save(); - - emit(user.username, "user", user); - emit(user.username, "phoneVerified", true); - res.end(); - } else { - res.status(500).send("invalid token or username"); - } - }); - - app.post("/openchannel", auth, async (req, res) => { - let err = m => res.status(500).send(m); - if (req.user.balance < 10000) { - return err("Need at least 10000 satoshis for channel opening"); - } - - let pending = await lna.pendingChannels({}, lna.meta); - let busypeers = pending.pending_open_channels.map( - c => c.channel.remote_node_pub - ); - let peer = lna.channelpeers.find(p => !busypeers.includes(p)); - let sent = false; - - l("channeling"); - - if (!peer) { - res - .status(500) - .send("All peers have pending channel requests, try again later"); - return; - } - - try { - let amount = Math.min(req.user.balance, 16777216); - - let openchannel = async (peer, amount) => { - let channel = await lna.openChannel({ - node_pubkey: new Buffer(peer, "hex"), - local_funding_amount: amount - }); - - channel.on("data", async data => { - if (sent || !data.chan_pending) return; - req.user.balance += amount; - req.user.balance -= amount; - req.user.channel = reverse(data.chan_pending.txid).toString("hex"); - await req.user.save(); - emit(req.user.username, "user", req.user); - res.send(data); - sent = true; - }); - - channel.on("error", err => { - l("channel error", peer, err); - let msg = err.message; - - if ( - msg.startsWith("Multiple") || - msg.startsWith("You gave") || - msg.startsWith("peer") - ) { - busypeers.push(peer); - peer = lna.channelpeers.find(p => !busypeers.includes(p)); - if (peer) { - return openchannel(peer, amount); - } else { - return res - .status(500) - .send( - "All peers are busy, couldn't open a channel, wait a few blocks and try again" - ); - } - } - - if (msg.startsWith("not enough")) { - msg = "Server wallet is busy. Wait for a block and try again"; - } - - return res.status(500).send(msg); - }); - }; - - openchannel(peer, amount); - } catch (err) { - l("rpc error", peer, err); - return res.status(500).send(err); - } - }); - - app.get("/channels", auth, async (req, res) => { - return res.send(await lna.listChannels()); - }); - - app.get("/peers", auth, async (req, res) => { - return res.send(await lna.listPeers()); - }); - - app.post("/sendPayment", auth, async (req, res) => { - let hash = req.body.payreq; - let payreq = bolt11.decode(hash); - l("sending lightning", req.user.username, payreq.satoshis); - - if (seen.includes(hash)) { - return res.status(500).send("Invoice has been paid, can't pay again"); - } - - try { - await db.transaction(async transaction => { - let { balance } = await db.User.findOne( - { - where: { - username: req.user.username - } - }, - { transaction } - ); - - if (balance < payreq.satoshis) { - throw new Error(); - } - - req.user.balance -= payreq.satoshis; - await req.user.save({ transaction }); - }); - } catch (e) { - return res.status(500).send("Not enough satoshis"); - } - - const stream = lna.sendPayment(lna.meta, {}); - stream.write({ payment_request: req.body.payreq }); - - stream.on("data", async m => { - if (m.payment_error) { - res.status(500).send(m.payment_error); - } else { - if (seen.includes(m.payment_preimage)) return; - seen.push(m.payment_preimage); - - let total = parseInt(m.payment_route.total_amt); - req.user.balance -= total - payreq.satoshis; - - await db.transaction(async transaction => { - await req.user.save({ transaction }); - - await db.Payment.create( - { - amount: -total, - user_id: req.user.id, - hash, - rate: app.get("rates").ask, - currency: "CAD" - }, - { transaction } - ); - }); - - emit(req.user.username, "user", req.user); - - if (payreq.payeeNodeKey === config.lnb.id) { - let invoice = await lna.addInvoice({ value: payreq.satoshis }); - let payback = lnb.sendPayment(lnb.meta, {}); - - /* eslint-disable-next-line */ - let { payment_request } = invoice; - /* eslint-disable-next-line */ - payback.write({ payment_request }); - } - - seen.push(hash); - res.send(m); - } - }); - - stream.on("error", e => { - let msg = e.message; - - res.status(500).send(msg); - }); - }); - - app.get("/friends", auth, async (req, res) => { - try { - let friends = (await fb.api( - `/${req.user.username}/friends?access_token=${req.user.fbtoken}` - )).data; - friends = await Promise.all( - friends.map(async f => { - let pic = (await fb.api( - `/${f.id}/picture?redirect=false&type=small&access_token=${req.user.fbtoken}` - )).data; - f.pic = pic.url; - return f; - }) - ); - - res.send(friends); - } catch (e) { - res - .status(500) - .send("There was a problem getting your facebook friends: ", e); - } - }); - - app.post("/payUser", auth, async (req, res) => { - let { payuser, amount } = req.body; - - let user = await db.User.findOne({ - where: { - username: payuser - } - }); - - if (!user) { - return res - .status(500) - .send("Couldn't find the user you're trying to pay"); - } - let err = m => res.status(500).send(m); - - let invoice; - try { - invoice = await lnb.addInvoice({ value: amount }); - } catch (e) { - return err(e.message); - } - - let hash = invoice.payment_request; - - await db.Payment.create({ - user_id: user.id, - hash, - amount, - currency: "CAD", - rate: app.get("rates").ask, - tip: 0 - }); - - req.url = "/sendPayment"; - req.body.payreq = invoice.payment_request; - return app._router.handle(req, res); - }); - - app.post("/sendCoins", auth, async (req, res) => { - const MINFEE = 3000; - - let { address, amount } = req.body; - - l("sending coins", req.user.username, amount, address); - - if (amount === req.user.balance) { - amount = req.user.balance - MINFEE; - } - - try { - await db.transaction(async transaction => { - let { balance } = await db.User.findOne( - { - where: { - username: req.user.username - } - }, - { transaction } - ); - - if (amount > balance) { - l("amount exceeds balance", amount, balance); - throw new Error("insufficient funds"); - } - - req.user.balance -= parseInt(amount) + 10000; - await req.user.save({ transaction }); - }); - } catch (e) { - return res.status(500).send("Not enough satoshis"); - } - - try { - if (config.bitcoin.walletpass) - await bc.walletPassphrase(config.bitcoin.walletpass, 300); - - let txid = await bc.sendToAddress( - address, - (amount / 100000000).toFixed(8) - ); - - let txhex = await bc.getRawTransaction(txid); - let tx = bitcoin.Transaction.fromHex(txhex); - - let inputTotal = await tx.ins.reduce(async (a, input) => { - let h = await bc.getRawTransaction(reverse(input.hash).toString("hex")); - return a + bitcoin.Transaction.fromHex(h).outs[input.index].value; - }, 0); - let outputTotal = tx.outs.reduce((a, b) => a + b.value, 0); - - let fees = inputTotal - outputTotal; - let total = Math.min(parseInt(amount) + fees, req.user.balance); - req.user.balance += 10000 - fees; - if (req.user.balance < 0) req.user.balance = 0; - - await db.transaction(async transaction => { - await req.user.save({ transaction }); - emit(req.user.username, "user", req.user); - - await db.Payment.create( - { - amount: -total, - user_id: req.user.id, - hash: txid, - rate: app.get("rates").ask, - currency: "CAD" - }, - { transaction } - ); - }); - - res.send({ txid, tx, amount, fees }); - } catch (e) { - l(e); - res.status(500).send(e.message); - } - }); - - app.post("/addInvoice", auth, async (req, res) => { - let err = m => res.status(500).send(m); - let { amount, address, tip } = req.body; - - let invoice; - try { - invoice = await lnb.addInvoice({ value: amount }); - } catch (e) { - return err(e.message); - } - - let hash = invoice.payment_request; - if (address) hash = address; - - await db.Payment.create({ - user_id: req.user.id, - hash, - amount, - currency: "CAD", - rate: app.get("rates").ask, - tip - }); - - res.send(invoice); - }); - - let fetchRates; - (fetchRates = async () => { - try { - let res = await axios.get( - "https://api.kraken.com/0/public/Ticker?pair=XBTCAD" - ); - let ask = res.data.result.XXBTZCAD.c[0]; - let now = new Date(); - let ts = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`; - l(ts, "ask price:", ask); - app.set("rates", { ask }); - socket.emit("rate", ask); - } catch (e) { - l(e); - } - - setTimeout(fetchRates, 3000); - })(); - app.get("/rates", (req, res) => { res.send(app.get("rates")); }); - app.post("/login", async (req, res) => { - try { - let user = await db.User.findOne({ - where: { - username: req.body.username - } - }); - - if ( - !user || - !(await bcrypt.compare(req.body.password, user.password)) || - (user.twofa && !(await authyVerify(user))) - ) { - return res.status(401).end(); - } - - let payload = { username: user.username }; - let token = jwt.sign(payload, config.jwt); - res.cookie("token", token, { expires: new Date(Date.now() + 432000000) }); - - user = pick(user, ...whitelist); - res.send({ user, token }); - } catch (err) { - res.status(401).end(); - } - }); - - app.post("/facebookLogin", async (req, res) => { - let { accessToken, userID } = req.body; - - let url = `https://graph.facebook.com/debug_token?input_token=${accessToken}&access_token=${config.facebook.appToken}`; - let check = await axios.get(url); - if (!check.data.data.is_valid) return res.status(401).end(); - - try { - let user = await db.User.findOne({ - where: { - username: req.body.userID - } - }); - - if (!user) { - user = await db.User.create(user); - user.username = userID; - user.name = (await fb.api(`/me?access_token=${accessToken}`)).name; - user.address = (await lna.newAddress({ type: 1 }, lna.meta)).address; - user.password = await bcrypt.hash(accessToken, 1); - user.balance = 0; - user.pending = 0; - let friends = (await fb.api( - `/${userID}/friends?access_token=${accessToken}` - )).data; - if (friends.find(f => f.id === config.facebook.specialFriend)) { - user.friend = true; - user.limit = 200; - } - await user.save(); - addresses[user.address] = user.username; - } - - user.pic = (await fb.api( - `/me/picture?access_token=${accessToken}&redirect=false` - )).data.url; - user.fbtoken = accessToken; - await user.save(); - - if (user.twofa && !(await authyVerify(user))) res.status(401).end(); - - let payload = { username: user.username }; - let token = jwt.sign(payload, config.jwt); - res.cookie("token", token, { expires: new Date(Date.now() + 432000000) }); - res.send({ user, token }); - } catch (err) { - l(err); - res.status(401).end(); - } - }); - - app.post("/buy", auth, async (req, res) => { - const stripe = require("stripe")(config.stripe); - const { token, amount, sat } = req.body; - let dollarAmount = parseInt(amount / 100); - - if (dollarAmount > req.user.limit) return res.status(500).end(); - - try { - const charge = await stripe.charges.create({ - amount, - currency: "cad", - description: "Bitcoin", - source: token - }); - - req.user.balance += parseInt(sat); - req.user.limit -= dollarAmount; - await req.user.save(); - emit(req.user.username, "user", req.user); - - await db.Payment.create({ - user_id: req.user.id, - hash: charge.balance_transaction, - amount: parseInt(sat), - currency: "CAD", - rate: app.get("rates").ask, - received: true, - tip: 0 - }); - - res.send(`Bought ${amount}`); - } catch (e) { - console.log(e); - res.status(500).send(e); - } - }); - - app.post("/order", auth, async (req, res) => { - await db.Order.create({ - amount: 1, - price: 2, - type: "BUY", - pair: "BTC/CAD" - }); - }); - - app.get("/me", async (req, res) => { - let data = (await fb.api( - "/me/picture?access_token=EAAEIFqWk3ZAwBAEUfxQdH3T5CBKXmU8d7jQ5OTJBJZBiU1ZAp76lO26nh57WolM4R4JoKks9BCc49s8VrlEm2Ub1GlZCEzVD9fGxzUiranXDErDmR5gDUPKX3BhCsGA649a4hmbldRwKFTsmZCGZCergm9ACspKdTZB0WgFgA9wEdemIRIXuwCygNrymmKDh0Wd8nmoT4Hj3wZDZD&redirect=false" - )).data; - res.send(data.url); - }); - - app.get("/exchanges", async (req, res) => { - let r = await axios.get( - "https://min-api.cryptocompare.com/data/v2/all/exchanges?fsym=BTC&api_key=6dc6d605ac45dcdbbc44d67111a2f03ba42ca8f54c5d4bf21069d2d3e99a89f0" - ); - res.send(r.data); - }); + require("./payments")(app, auth, addresses, bc, db, emit, seen, payments); + require("./users")(addresses, auth, app, bc, db, emit); + require("./stripe")(auth, app, db, emit); - app.get("/currencies", async (req, res) => { - res.send(["CAD", "USD"]); - }); - - app.get("/balance/:address", async (req, res) => { - let network = config.bitcoin.network === "mainnet" ? "main" : "test3"; - let { address } = req.params; - - try { - res.send( - (await axios.get( - `https://api.blockcypher.com/v1/btc/${network}/addrs/${address}/balance` - )).data - ); - } catch (e) { - res.status(500).send("Problem getting address balance"); - } - }); + app.get("/balance/:address", require("./addressBalance")); app.use(function(err, req, res, next) { res.status(500); |
