diff options
| author | Adam Soltys <[email protected]> | 2020-03-11 15:10:40 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2020-03-11 15:10:40 -0700 |
| commit | c0888fab024cc9f7c99812bfabf9d7774312dbf7 (patch) | |
| tree | cad95683af97432f6092a2c2bae10fbb7a588db5 | |
| parent | bf2b51f0c2ae9b8f0f330dde6411e45fc0da8a25 (diff) | |
| download | coinos-server-c0888fab024cc9f7c99812bfabf9d7774312dbf7.tar.gz coinos-server-c0888fab024cc9f7c99812bfabf9d7774312dbf7.zip | |
refactor
51 files changed, 1321 insertions, 1853 deletions
@@ -5,7 +5,6 @@ npm-debug.log test/unit/coverage test/e2e/reports selenium-debug.log -db config.js .env config/config.json diff --git a/bitcoinPayments.js b/bitcoinPayments.js deleted file mode 100644 index 0d410a6a..00000000 --- a/bitcoinPayments.js +++ /dev/null @@ -1,148 +0,0 @@ -const reverse = require("buffer-reverse"); -const zmq = require("zeromq"); -const config = require("./config"); -const Sequelize = require("sequelize"); - -const bitcoin = require("bitcoinjs-lib"); -const l = require("pino")(); -const toSats = n => parseInt((n * 100000000).toFixed()) - -const zmqRawBlock = zmq.socket("sub"); -zmqRawBlock.connect(config.bitcoin.zmqrawblock); -zmqRawBlock.subscribe("rawblock"); - -const zmqRawTx = zmq.socket("sub"); -zmqRawTx.connect(config.bitcoin.zmqrawtx); -zmqRawTx.subscribe("rawtx"); - -let NETWORK = bitcoin.networks[config.bitcoin.network === "mainnet" ? "bitcoin" : config.bitcoin.network]; - -module.exports = (app, bc, db, addresses, payments, emit) => { - zmqRawTx.on("message", async (topic, message, sequence) => { - const hex = message.toString("hex"); - let tx = bitcoin.Transaction.fromHex(message); - let hash = reverse(tx.getHash()).toString("hex"); - - if (payments.includes(hash)) return; - - Promise.all( - tx.outs.map(async o => { - const { value } = o; - - let address; - try { - address = bitcoin.address.fromOutputScript( - o.script, - NETWORK - ); - } catch (e) { - return; - } - - if (Object.keys(addresses).includes(address)) { - payments.push(hash); - - let user = await db.User.findOne({ - where: { - username: addresses[address] - } - }); - - let invoices = await db.Payment.findAll({ - limit: 1, - where: { - address, - received: null, - amount: { - [Sequelize.Op.gt]: 0 - } - }, - order: [["createdAt", "DESC"]] - }); - - let tip = null; - if (invoices.length) tip = invoices[0].tip; - - let confirmed = false; - - user.address = await bc.getNewAddress("", "bech32"); - user.pending += value; - - await user.save(); - emit(user.username, "user", user); - - addresses[user.address] = user.username; - - const payment = await db.Payment.create({ - user_id: user.id, - hash, - amount: value, - currency: user.currency, - rate: app.get("rates")[user.currency], - received: true, - tip, - confirmed, - address, - asset: 'BTC', - }); - - l.info("bitcoin detected", user.username, o.value); - emit(user.username, "payment", payment); - } - }) - ); - }); - - let queue = {}; - - zmqRawBlock.on("message", async (topic, message, sequence) => { - const payments = await db.Payment.findAll({ - where: { confirmed: false } - }); - - const hashes = payments.map(p => p.hash); - - let block = bitcoin.Block.fromHex(message.toString("hex")); - block.transactions.map(tx => { - let hash = reverse(tx.getHash()).toString("hex"); - if (hashes.includes(hash)) queue[hash] = 1; - }); - }); - - setInterval(async () => { - let arr = Object.keys(queue); - for (let i = 0; i < arr.length; i++) { - let hash = arr[i]; - - let p = await db.Payment.findOne({ - include: [{ model: db.User, as: "user" }], - where: { hash, confirmed: 0 } - }) - - p.confirmed = 1; - - let user = await p.getUser(); - user.balance += p.amount; - user.pending -= Math.min(user.pending, p.amount); - emit(user.username, "user", user); - - await user.save(); - await p.save(); - - let payments = await db.Payment.findAll({ - where: { - user_id: user.id, - received: { - [Sequelize.Op.ne]: null - }, - }, - order: [['id', 'DESC']], - limit: 12 - }); - - l.info("bitcoin confirmed", user.username, p.amount); - emit(user.username, "payments", payments); - delete queue[hash]; - } - }, 1000); -}; diff --git a/rpc.proto b/config/rpc.proto index 76a78450..76a78450 100644 --- a/rpc.proto +++ b/config/rpc.proto @@ -1,16 +1,15 @@ -const config = require("./config"); const Sequelize = require("sequelize"); const conf = config.dbOptions; -const db = new Sequelize(conf.database, conf.username, conf.password, { +db = new Sequelize(conf.database, conf.username, conf.password, { host: conf.host, dialect: conf.dialect, logging: false, dialectOptions: { multipleStatements: true } }); -db["User"] = require("./models/users.js")(db); -db["Payment"] = require("./models/payments.js")(db); +require("./models/users.js"); +require("./models/payments.js"); db["User"].hasMany(db["Payment"], { as: "payments", @@ -21,5 +20,3 @@ db["Payment"].belongsTo(db["User"], { as: "user", foreignKey: "user_id" }); - -module.exports = db; diff --git a/db/models/payments.js b/db/models/payments.js new file mode 100644 index 00000000..c6dd6e83 --- /dev/null +++ b/db/models/payments.js @@ -0,0 +1,138 @@ +const { DataTypes } = require('sequelize'); + +const attributes = { + id: { + type: DataTypes.INTEGER(11), + allowNull: false, + defaultValue: null, + primaryKey: true, + autoIncrement: true, + comment: null, + field: "id" + }, + user_id: { + type: DataTypes.INTEGER(11), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "user_id" + }, + hash: { + type: DataTypes.TEXT, + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "hash" + }, + createdAt: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "createdAt" + }, + updatedAt: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "updatedAt" + }, + rate: { + type: DataTypes.DOUBLE, + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "rate" + }, + asset: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "asset" + }, + currency: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "currency" + }, + address: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "address" + }, + received: { + type: DataTypes.INTEGER(1), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "received" + }, + amount: { + type: DataTypes.DOUBLE, + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "amount" + }, + fee: { + type: DataTypes.DOUBLE, + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "fee" + }, + tip: { + type: DataTypes.DOUBLE, + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "tip" + }, + confirmed: { + type: DataTypes.INTEGER(1), + allowNull: false, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "confirmed" + } +}; + +const options = { + tableName: "payments", + comment: "", + indexes: [] +}; + +db["Payment"] = db.define("payments_model", attributes, options); diff --git a/db/models/users.js b/db/models/users.js new file mode 100644 index 00000000..d5b1f5b8 --- /dev/null +++ b/db/models/users.js @@ -0,0 +1,246 @@ +const { DataTypes } = require('sequelize'); + +const attributes = { + id: { + type: DataTypes.INTEGER(11), + allowNull: false, + defaultValue: null, + primaryKey: true, + autoIncrement: true, + comment: null, + field: "id" + }, + username: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "username" + }, + password: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "password" + }, + liquid: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "liquid" + }, + confidential: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "confidential" + }, + address: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "address" + }, + symbol: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "symbol" + }, + otpsecret: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: "CAD", + primaryKey: false, + autoIncrement: false, + comment: null, + field: "otpsecret" + }, + currency: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: "CAD", + primaryKey: false, + autoIncrement: false, + comment: null, + field: "currency" + }, + currencies: { + type: DataTypes.JSON, + allowNull: false, + defaultValue: ["USD"], + primaryKey: false, + autoIncrement: false, + comment: null, + field: "currencies" + }, + pubkey: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "pubkey" + }, + privkey: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "privkey" + }, + createdAt: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "createdAt" + }, + updatedAt: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "updatedAt" + }, + index: { + type: DataTypes.INTEGER(11), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "index" + }, + balance: { + type: DataTypes.INTEGER(11), + allowNull: false, + defaultValue: "0", + primaryKey: false, + autoIncrement: false, + comment: null, + field: "balance" + }, + channel: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "channel" + }, + readonlypw: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "readonlypw" + }, + limit: { + type: DataTypes.INTEGER(11), + allowNull: true, + defaultValue: "0", + primaryKey: false, + autoIncrement: false, + comment: null, + field: "limit" + }, + fbtoken: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "fbtoken" + }, + friend: { + type: DataTypes.INTEGER(1), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "friend" + }, + pending: { + type: DataTypes.INTEGER(11), + allowNull: true, + defaultValue: "0", + primaryKey: false, + autoIncrement: false, + comment: null, + field: "pending" + }, + name: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "name" + }, + pic: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "pic" + }, + twofa: { + type: DataTypes.INTEGER(1), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "twofa" + }, + pin: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "pin" + }, +}; + +const options = { + tableName: "users", + comment: "", + indexes: [] +}; + +db["User"] = db.define("users_model", attributes, options); diff --git a/schema.sql b/db/schema.sql index 7bdf7d3b..7bdf7d3b 100644 --- a/schema.sql +++ b/db/schema.sql @@ -1,63 +1,40 @@ const bodyParser = require("body-parser"); const compression = require("compression"); const cookieParser = require("cookie-parser"); -const BitcoinCore = require("bitcoin-core"); const cors = require("cors"); const express = require("express"); -const config = require("./config"); -const bolt11 = require("bolt11"); +l = require("pino")(); +config = require("./config"); -const l = require("pino")(); +SATS = 100000000; +toSats = n => parseInt((n * SATS).toFixed()) -(async () => { - const bc = new BitcoinCore(config.bitcoin); - const app = express(); - app.enable("trust proxy"); - app.use(bodyParser.urlencoded({ extended: true })); - app.use(bodyParser.json()); - app.use(cookieParser()); - app.use(cors({ credentials: true, origin: "http://*:*" })); - app.use(compression()); +app = express(); +app.enable("trust proxy"); +app.use(bodyParser.urlencoded({ extended: true })); +app.use(bodyParser.json()); +app.use(cookieParser()); +app.use(cors({ credentials: true, origin: "http://*:*" })); +app.use(compression()); - const server = require("http").Server(app); - const db = require("./db"); - const [socket, emit] = require("./sockets")(app, db, server); - const passport = require("./passport")(db); - const auth = passport.authenticate("jwt", { session: false }); +server = require("http").Server(app); +require("./db"); +require("./lib/sockets"); +require("./lib/passport"); +require("./lib/rates"); - app.use(passport.initialize()); +require("./routes/payments"); +require("./routes/users"); - require("./rates")(app, socket); +app.use((err, req, res, next) => { + l.info("res", res); + res.status(500); + res.send("An error occurred"); + l.error("uncaught error", err.stack); + return res.end(); +}); - const seen = []; - const addresses = {}; - await db.User.findAll({ - attributes: ["username", "address", "liquid"] - }).map(u => { - addresses[u.address] = u.username; - if (u.liquid) addresses[u.liquid] = u.username; - }); - - const payments = (await db.Payment.findAll({ - attributes: ["hash"] - })).map(p => p.hash); - - app.get("/rates", (req, res) => { - res.send(app.get("rates")); - }); - - require("./payments")(app, auth, addresses, bc, db, emit, seen, payments); - require("./users")(addresses, auth, app, bc, db, emit); - - app.use(function(err, req, res, next) { - res.status(500); - res.send("An error occurred"); - l.error(err.stack); - return res.end(); - }); - - server.listen(config.port, () => - console.log(`CoinOS Server listening on port ${config.port}`) - ); -})(); +server.listen(config.port, () => + console.log(`CoinOS Server listening on port ${config.port}`) +); diff --git a/binance.js b/lib/binance.js index ccb010e1..2f4f7ef7 100644 --- a/binance.js +++ b/lib/binance.js @@ -1,8 +1,7 @@ const WebSocket = require("ws"); -const l = require("pino")(); -module.exports = (app, socket) => { - const binance = new WebSocket( +module.exports = () => { + binance = new WebSocket( "wss://stream.binance.com:9443/ws/btcusdt@ticker" ); diff --git a/devrates.js b/lib/devrates.js index 817db79d..817db79d 100644 --- a/devrates.js +++ b/lib/devrates.js diff --git a/lib/passport.js b/lib/passport.js new file mode 100644 index 00000000..1e4fd8b8 --- /dev/null +++ b/lib/passport.js @@ -0,0 +1,36 @@ +const Passport = require("passport"); +const jwt = require("passport-jwt"); + +function cookieExtractor(req) { + if (req && req.cookies) return req.cookies["token"]; + return null; +} + +passport = Passport.use( + new jwt.Strategy( + { + jwtFromRequest: jwt.ExtractJwt.fromExtractors([ + jwt.ExtractJwt.fromAuthHeaderAsBearerToken(), + cookieExtractor + ]), + secretOrKey: config.jwt + }, + async (payload, next) => { + try { + let user = await db.User.findOne({ + where: { + username: payload.username + } + }); + next(null, user); + } catch (e) { + l.error("error finding user for session", e); + next(null, { username: "guest" }); + } + } + ) +); + +auth = passport.authenticate("jwt", { session: false }); +app.use(passport.initialize()); + diff --git a/lib/rates.js b/lib/rates.js new file mode 100644 index 00000000..702e586e --- /dev/null +++ b/lib/rates.js @@ -0,0 +1,38 @@ +const axios = require("axios"); + +let binance = require("./binance")(); + +let fetchRates; +(fetchRates = async () => { + let rates = require("./devrates"); + + if (process.env.NODE_ENV === "production") { + try { + let res = await axios.get( + `http://data.fixer.io/api/latest?access_key=${config.fixer}` + ); + + let fx = res.data.rates; + + fx && + Object.keys(fx).map(symbol => { + rates[symbol] = fx[symbol] / fx["USD"]; + }); + } catch (e) { + l.error("error fetching rates", e); + } + } + + app.set("fxrates", rates); + + setTimeout(fetchRates, 7200000); +})(); + +setInterval(() => { + socket.emit("rates", app.get("rates")); +}, 1000); + +setInterval(() => { + binance.terminate(); + binance = require("./binance")(); +}, 360000); diff --git a/lib/sockets.js b/lib/sockets.js new file mode 100644 index 00000000..3ebf86d7 --- /dev/null +++ b/lib/sockets.js @@ -0,0 +1,67 @@ +const jwt = require("jsonwebtoken"); +const io = require("socket.io"); +const Sequelize = require("sequelize"); + +/* eslint-disable-next-line */ +const pick = (O, ...K) => K.reduce((o, k) => ((o[k] = O[k]), o), {}); +const sids = {}; + +socket = io(server, { origins: "*:*" }); +emit = (username, msg, data) => { + if (data.username !== undefined) data = pick(data, ...whitelist); + + if (!sids[username]) return; + sids[username].map((sid, i) => { + try { + socket.to(sid).emit(msg, data); + } catch (e) { + sids[username].splice(i, 1); + } + }); +}; + +socket.use((socket, next) => { + try { + let token = socket.handshake.query.token; + let user = jwt.decode(token).username; + socket.request.user = user; + sids[user] ? sids[user].push(socket.id) : (sids[user] = [socket.id]); + sids[socket.id] = user; + } catch (e) { + l.error("error creating socket", e); + } + next(); +}); + +socket.sockets.on("connect", async socket => { + socket.emit("connected"); + if (app.get("rates")) socket.emit("rate", app.get("rates").ask); + socket.on("getuser", async (data, callback) => { + l.info("socket open", socket.request.user); + const user = await db.User.findOne({ + include: [ + { + model: db.Payment, + as: "payments", + order: [["id", "DESC"]], + received: { + [Sequelize.Op.ne]: null + }, + limit: 12 + } + ], + where: { + username: socket.request.user + } + }); + + callback(user); + }); + + socket.on("disconnect", s => { + let user = sids[socket.id]; + l.info("socket closed", user); + sids[user].splice(sids[user].indexOf(socket.id), 1); + delete sids[socket.id]; + }); +}); diff --git a/whitelist.js b/lib/whitelist.js index 23c46459..bdba4a95 100644 --- a/whitelist.js +++ b/lib/whitelist.js @@ -1,4 +1,4 @@ -module.exports = [ +whitelist = [ 'id', 'fbtoken', 'username', diff --git a/liquidPayments.js b/liquidPayments.js deleted file mode 100644 index 3c2a12f4..00000000 --- a/liquidPayments.js +++ /dev/null @@ -1,145 +0,0 @@ -const reverse = require("buffer-reverse"); -const zmq = require("zeromq"); -const config = require("./config"); -const Sequelize = require("sequelize"); -const BitcoinCore = require("bitcoin-core"); -const bitcoin = require("elementsjs-lib"); -const l = require("pino")(); - -const zmqRawBlock = zmq.socket("sub"); -zmqRawBlock.connect(config.liquid.zmqrawblock); -zmqRawBlock.subscribe("rawblock"); - -const zmqRawTx = zmq.socket("sub"); -zmqRawTx.connect("tcp://127.0.0.1:18603"); -zmqRawTx.subscribe("rawtx"); - -let NETWORK = bitcoin.networks[config.bitcoin.network === "mainnet" ? "bitcoin" : config.bitcoin.network]; -const bc = new BitcoinCore(config.liquid); - -const toSats = n => parseInt((n * 100000000).toFixed()) - -module.exports = (app, db, addresses, payments, emit) => { - zmqRawTx.on("message", async (topic, message, sequence) => { - const hex = message.toString("hex"); - const unblinded = await bc.unblindRawTransaction(hex); - const tx = await bc.decodeRawTransaction(unblinded.hex); - const blinded = await bc.decodeRawTransaction(hex); - if (payments.includes(blinded.txid)) return; - - Promise.all( - tx.vout.map(async o => { - if (!(o.scriptPubKey && o.scriptPubKey.addresses)) return; - const value = toSats(o.value); - const address = o.scriptPubKey.addresses[0]; - - if (Object.keys(addresses).includes(address)) { - let user = await db.User.findOne({ - where: { - username: addresses[address] - } - }); - - let invoices = await db.Payment.findAll({ - limit: 1, - where: { - address, - received: null, - amount: { - [Sequelize.Op.gt]: 0 - } - }, - order: [["id", "DESC"]] - }); - - let tip = null; - if (invoices.length) tip = invoices[0].tip; - - let confirmed = 0; - - user.pending += value; - user.confidential = await bc.getNewAddress(); - user.liquid = (await bc.getAddressInfo(user.confidential)).unconfidential; - - await user.save(); - emit(user.username, "user", user); - - addresses[user.liquid] = user.username; - - const payment = await db.Payment.create({ - user_id: user.id, - hash: blinded.txid, - amount: value, - currency: user.currency, - rate: app.get("rates")[user.currency], - received: true, - tip, - confirmed, - address, - asset: 'LBTC', - }); - payments.push(blinded.txid); - - l.info("liquid detected", user.username, value); - emit(user.username, "payment", payment); - } - }) - ); - }); - - let queue = {}; - - zmqRawBlock.on("message", async (topic, message, sequence) => { - const payments = await db.Payment.findAll({ - where: { confirmed: 0 } - }); - - const hashes = payments.map(p => p.hash); - - const block = bitcoin.Block.fromHex(message.toString("hex"), true); - const hash = await bc.getBlockHash(block.height); - const json = await bc.getBlock(hash, 2); - - json.tx.map(tx => { - if (hashes.includes(tx.txid)) queue[tx.txid] = 1; - }); - }); - - setInterval(async () => { - let arr = Object.keys(queue); - - for (let i = 0; i < arr.length; i++) { - let hash = arr[i]; - - let p = await db.Payment.findOne({ - include: [{ model: db.User, as: "user" }], - where: { hash, confirmed: 0, received: 1 } - }) - - p.confirmed = 1; - - let user = await p.getUser(); - user.balance += p.amount; - user.pending -= Math.min(user.pending, p.amount); - l.info("liquid confirmed", user.username, p.amount); - emit(user.username, "user", user); - - await user.save(); - await p.save(); - - let payments = await db.Payment.findAll({ - where: { - user_id: user.id, - received: { - [Sequelize.Op.ne]: null - }, - }, - order: [['id', 'DESC']], - limit: 12 - }); - - emit(user.username, "payments", payments); - delete queue[hash]; - } - }, 1000); -}; diff --git a/lnd.js b/lnd.js deleted file mode 100644 index 2505475a..00000000 --- a/lnd.js +++ /dev/null @@ -1,26 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const grpc = require('grpc'); -const protoLoader = require('@grpc/proto-loader'); - -module.exports = ({ tls, macaroon, server }) => { - const packageDefinition = protoLoader.loadSync('rpc.proto', { - keepCase: true, - longs: String, - enums: String, - defaults: true, - oneofs: true - }); - const lnrpc = grpc.loadPackageDefinition(packageDefinition).lnrpc; - process.env.GRPC_SSL_CIPHER_SUITES = 'HIGH+ECDSA' - const lndCert = fs.readFileSync(tls); - const sslCreds = grpc.credentials.createSsl(lndCert); - const macaroonCreds = grpc.credentials.createFromMetadataGenerator(function(args, callback) { - const macaroonFile = fs.readFileSync(macaroon).toString('hex'); - const metadata = new grpc.Metadata() - metadata.add('macaroon', macaroonFile); - callback(null, metadata); - }); - const creds = grpc.credentials.combineChannelCredentials(sslCreds, macaroonCreds); - return new lnrpc.Lightning(server, creds); -} diff --git a/migrations/20180128181238-create-user.js b/migrations/20180128181238-create-user.js deleted file mode 100644 index 50424e18..00000000 --- a/migrations/20180128181238-create-user.js +++ /dev/null @@ -1,60 +0,0 @@ -'use strict'; -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.createTable('users', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER - }, - username: { - type: Sequelize.STRING - }, - password: { - type: Sequelize.STRING - }, - email: { - type: Sequelize.STRING - }, - address: { - type: Sequelize.STRING - }, - unit: { - type: Sequelize.STRING - }, - symbol: { - type: Sequelize.STRING - }, - currency: { - type: Sequelize.STRING - }, - commission: { - type: Sequelize.STRING - }, - title: { - type: Sequelize.STRING - }, - pubkey: { - type: Sequelize.STRING - }, - privkey: { - type: Sequelize.STRING - }, - index: { - type: Sequelize.INTEGER - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE - } - }); - }, - down: (queryInterface, Sequelize) => { - return queryInterface.dropTable('users'); - } -}; diff --git a/migrations/20180206033508-create-transaction.js b/migrations/20180206033508-create-transaction.js deleted file mode 100644 index 06863a43..00000000 --- a/migrations/20180206033508-create-transaction.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.createTable('transactions', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER - }, - hash: { - type: Sequelize.STRING - }, - address: { - type: Sequelize.STRING - }, - date: { - type: Sequelize.DATE - }, - amount: { - type: Sequelize.FLOAT - }, - rate: { - type: Sequelize.FLOAT - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE - } - }); - }, - down: (queryInterface, Sequelize) => { - return queryInterface.dropTable('transactions'); - } -}; diff --git a/migrations/20180214033702-addUserBalance.js b/migrations/20180214033702-addUserBalance.js deleted file mode 100644 index 06ef5adb..00000000 --- a/migrations/20180214033702-addUserBalance.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.addColumn('users', 'balance', { type: Sequelize.INTEGER }); - }, - - down: (queryInterface, Sequelize) => { - return queryInterface.removeColumn('users', 'balance'); - } -}; diff --git a/migrations/20180215052148-addChannelPoint.js b/migrations/20180215052148-addChannelPoint.js deleted file mode 100644 index 3c175713..00000000 --- a/migrations/20180215052148-addChannelPoint.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.addColumn('users', 'channel', { type: Sequelize.STRING }) - }, - - down: (queryInterface, Sequelize) => { - return queryInterface.removeColumn('users', 'channel') - } -}; diff --git a/migrations/20180217065619-addChannelBalance.js b/migrations/20180217065619-addChannelBalance.js deleted file mode 100644 index fc909f53..00000000 --- a/migrations/20180217065619-addChannelBalance.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.addColumn('users', 'channelbalance', { type: Sequelize.INTEGER }) - }, - - down: (queryInterface, Sequelize) => { - return queryInterface.removeColumn('users', 'channelbalance') - } -}; diff --git a/migrations/20180221010609-createInvoices.js b/migrations/20180221010609-createInvoices.js deleted file mode 100644 index 3fbcfa21..00000000 --- a/migrations/20180221010609-createInvoices.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; - -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.createTable('invoices', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - user_id: { - type: Sequelize.INTEGER, - }, - payreq: { - type: Sequelize.STRING, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE - }, - }) - }, - - down: (queryInterface, Sequelize) => { - return queryInterface.dropTable('invoices') - }, -}; diff --git a/migrations/20180312160019-addInvoiceRateAndCurrency.js b/migrations/20180312160019-addInvoiceRateAndCurrency.js deleted file mode 100644 index 97dbc6ea..00000000 --- a/migrations/20180312160019-addInvoiceRateAndCurrency.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -module.exports = { - up: (queryInterface, Sequelize) => { - queryInterface.addColumn('invoices', 'rate', { type: Sequelize.DOUBLE }) - return queryInterface.addColumn('invoices', 'currency', { type: Sequelize.STRING }) - }, - - down: (queryInterface, Sequelize) => { - queryInterface.removeColumn('invoices', 'rate') - return queryInterface.removeColumn('invoices', 'currency') - } -}; diff --git a/migrations/20180315232457-renameInvoices.js b/migrations/20180315232457-renameInvoices.js deleted file mode 100644 index 9e06e56d..00000000 --- a/migrations/20180315232457-renameInvoices.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -module.exports = { - up: (queryInterface, Sequelize) => { - queryInterface.renameTable('invoices', 'payments') - queryInterface.addColumn('payments', 'address', { type: Sequelize.INTEGER }); - queryInterface.addColumn('payments', 'received', { type: Sequelize.BOOLEAN }); - }, - - down: (queryInterface, Sequelize) => { - queryInterface.removeColumn('payments', 'received'); - queryInterface.removeColumn('payments', 'address'); - queryInterface.renameTable('payments', 'invoices') - } -}; diff --git a/migrations/20180316032817-addPaymentsAmount.js b/migrations/20180316032817-addPaymentsAmount.js deleted file mode 100644 index 89356ea3..00000000 --- a/migrations/20180316032817-addPaymentsAmount.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.addColumn('payments', 'amount', { type: Sequelize.DOUBLE }); - }, - - down: (queryInterface, Sequelize) => { - return queryInterface.removeColumn('payments', 'amount'); - } -}; diff --git a/migrations/20180322163730-addTipToPayments.js b/migrations/20180322163730-addTipToPayments.js deleted file mode 100644 index a482c7d4..00000000 --- a/migrations/20180322163730-addTipToPayments.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.addColumn('payments', 'tip', { type: Sequelize.DOUBLE }) - }, - - down: (queryInterface, Sequelize) => { - return queryInterface.removeColumn('payments', 'tip'); - } -}; diff --git a/migrations/20181008052713-addUserChannels.js b/migrations/20181008052713-addUserChannels.js deleted file mode 100644 index 29692445..00000000 --- a/migrations/20181008052713-addUserChannels.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.createTable('users_channels', { user_id: Sequelize.INTEGER, channel_id: Sequelize.INTEGER }); - }, - - down: (queryInterface, Sequelize) => { - return queryInterface.dropTable('users_channels'); - } -}; diff --git a/migrations/20181008053428-addUsersBalances.js b/migrations/20181008053428-addUsersBalances.js deleted file mode 100644 index 5f528322..00000000 --- a/migrations/20181008053428-addUsersBalances.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.createTable('users_balances', { user_id: Sequelize.INTEGER, symbol: Sequelize.STRING, balance: Sequelize.INTEGER, pending: Sequelize.INTEGER }); - }, - - down: (queryInterface, Sequelize) => { - return queryInterface.dropTable('users_balances'); - } -}; diff --git a/migrations/20190113193839-create_orders.js b/migrations/20190113193839-create_orders.js deleted file mode 100644 index 01e1ffb1..00000000 --- a/migrations/20190113193839-create_orders.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.createTable('orders', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER - }, - amount: { type: Sequelize.INTEGER }, - price: { type: Sequelize.INTEGER }, - type: { type: Sequelize.STRING }, - pair: { type: Sequelize.STRING }, - createdAt: { - allowNull: false, - type: Sequelize.DATE - }, - }); - }, - down: (queryInterface, Sequelize) => { - return queryInterface.dropTable('orders'); - }, -}; diff --git a/models/payments.js b/models/payments.js deleted file mode 100644 index 213c11b4..00000000 --- a/models/payments.js +++ /dev/null @@ -1,141 +0,0 @@ -const { - DataTypes -} = require('sequelize'); - -module.exports = sequelize => { - const attributes = { - id: { - type: DataTypes.INTEGER(11), - allowNull: false, - defaultValue: null, - primaryKey: true, - autoIncrement: true, - comment: null, - field: "id" - }, - user_id: { - type: DataTypes.INTEGER(11), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "user_id" - }, - hash: { - type: DataTypes.TEXT, - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "hash" - }, - createdAt: { - type: DataTypes.DATE, - allowNull: false, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "createdAt" - }, - updatedAt: { - type: DataTypes.DATE, - allowNull: false, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "updatedAt" - }, - rate: { - type: DataTypes.DOUBLE, - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "rate" - }, - asset: { - type: DataTypes.STRING(255), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "asset" - }, - currency: { - type: DataTypes.STRING(255), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "currency" - }, - address: { - type: DataTypes.STRING(255), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "address" - }, - received: { - type: DataTypes.INTEGER(1), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "received" - }, - amount: { - type: DataTypes.DOUBLE, - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "amount" - }, - fee: { - type: DataTypes.DOUBLE, - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "fee" - }, - tip: { - type: DataTypes.DOUBLE, - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "tip" - }, - confirmed: { - type: DataTypes.INTEGER(1), - allowNull: false, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "confirmed" - } - }; - const options = { - tableName: "payments", - comment: "", - indexes: [] - }; - const PaymentsModel = sequelize.define("payments_model", attributes, options); - return PaymentsModel; -}; diff --git a/models/users.js b/models/users.js deleted file mode 100644 index 7adeeaf6..00000000 --- a/models/users.js +++ /dev/null @@ -1,249 +0,0 @@ -const { - DataTypes -} = require('sequelize'); - -module.exports = sequelize => { - const attributes = { - id: { - type: DataTypes.INTEGER(11), - allowNull: false, - defaultValue: null, - primaryKey: true, - autoIncrement: true, - comment: null, - field: "id" - }, - username: { - type: DataTypes.STRING(255), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "username" - }, - password: { - type: DataTypes.STRING(255), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "password" - }, - liquid: { - type: DataTypes.STRING(255), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "liquid" - }, - confidential: { - type: DataTypes.STRING(255), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "confidential" - }, - address: { - type: DataTypes.STRING(255), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "address" - }, - symbol: { - type: DataTypes.STRING(255), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "symbol" - }, - otpsecret: { - type: DataTypes.STRING(255), - allowNull: true, - defaultValue: "CAD", - primaryKey: false, - autoIncrement: false, - comment: null, - field: "otpsecret" - }, - currency: { - type: DataTypes.STRING(255), - allowNull: true, - defaultValue: "CAD", - primaryKey: false, - autoIncrement: false, - comment: null, - field: "currency" - }, - currencies: { - type: DataTypes.JSON, - allowNull: false, - defaultValue: ["USD"], - primaryKey: false, - autoIncrement: false, - comment: null, - field: "currencies" - }, - pubkey: { - type: DataTypes.STRING(255), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "pubkey" - }, - privkey: { - type: DataTypes.STRING(255), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "privkey" - }, - createdAt: { - type: DataTypes.DATE, - allowNull: false, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "createdAt" - }, - updatedAt: { - type: DataTypes.DATE, - allowNull: false, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "updatedAt" - }, - index: { - type: DataTypes.INTEGER(11), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "index" - }, - balance: { - type: DataTypes.INTEGER(11), - allowNull: false, - defaultValue: "0", - primaryKey: false, - autoIncrement: false, - comment: null, - field: "balance" - }, - channel: { - type: DataTypes.STRING(255), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "channel" - }, - readonlypw: { - type: DataTypes.STRING(255), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "readonlypw" - }, - limit: { - type: DataTypes.INTEGER(11), - allowNull: true, - defaultValue: "0", - primaryKey: false, - autoIncrement: false, - comment: null, - field: "limit" - }, - fbtoken: { - type: DataTypes.STRING(255), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "fbtoken" - }, - friend: { - type: DataTypes.INTEGER(1), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "friend" - }, - pending: { - type: DataTypes.INTEGER(11), - allowNull: true, - defaultValue: "0", - primaryKey: false, - autoIncrement: false, - comment: null, - field: "pending" - }, - name: { - type: DataTypes.STRING(255), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "name" - }, - pic: { - type: DataTypes.STRING(255), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "pic" - }, - twofa: { - type: DataTypes.INTEGER(1), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "twofa" - }, - pin: { - type: DataTypes.STRING(255), - allowNull: true, - defaultValue: null, - primaryKey: false, - autoIncrement: false, - comment: null, - field: "pin" - }, - }; - const options = { - tableName: "users", - comment: "", - indexes: [] - }; - const UsersModel = sequelize.define("users_model", attributes, options); - return UsersModel; -}; diff --git a/passport.js b/passport.js deleted file mode 100644 index df72c6a5..00000000 --- a/passport.js +++ /dev/null @@ -1,39 +0,0 @@ -const passport = require("passport"); -const jwt = require("passport-jwt"); -const config = require("./config"); - -const l = require("pino")(); - -function cookieExtractor(req) { - if (req && req.cookies) return req.cookies["token"]; - return null; -} - -module.exports = db => { - passport.use( - new jwt.Strategy( - { - jwtFromRequest: jwt.ExtractJwt.fromExtractors([ - jwt.ExtractJwt.fromAuthHeaderAsBearerToken(), - cookieExtractor - ]), - secretOrKey: config.jwt - }, - async (payload, next) => { - try { - let user = await db.User.findOne({ - where: { - username: payload.username - } - }); - next(null, user); - } catch (e) { - l.error("error finding user for session", e); - next(null, { username: "guest" }); - } - } - ) - ); - - return passport; -}; diff --git a/payments.js b/payments.js deleted file mode 100644 index c2d9d306..00000000 --- a/payments.js +++ /dev/null @@ -1,41 +0,0 @@ -const createLnrpc = require("./lib/createLnrpc"); -const config = require("./config"); -const lnd = require("./lnd"); - -module.exports = async (app, auth, addresses, bc, db, emit, seen, payments) => { - const lna = await createLnrpc(config.lna); - const lnb = await createLnrpc(config.lnb); - - const lnaraw = lnd(config.lna); - const lnbraw = lnd(config.lnb); - - require("./bitcoinPayments")(app, bc, db, addresses, payments, emit); - require("./liquidPayments")(app, db, addresses, payments, emit); - require("./lightningPayments")(app, db, lna, lnb, emit, payments); - - app.post( - "/queryRoutes", - auth, - require("./queryRoutes")(lnaraw) - ); - app.post( - "/sendPayment", - auth, - require("./sendPayment")(app, db, emit, seen, lnaraw, lnb) - ); - app.post("/payUser", auth, require("./payUser")(app, db, lna, lnb)); - app.post("/sendCoins", auth, require("./sendCoins")(addresses, app, bc, db, emit)); - app.post("/estimateBitcoinFee", auth, require("./estimateBitcoinFee")(app, bc, db, emit)); - app.post("/sendLiquid", auth, require("./sendLiquid")(addresses, app, db, emit)); - app.post("/estimateLiquidFee", auth, require("./estimateLiquidFee")(app, db, emit)); - app.post("/addInvoice", auth, require("./addInvoice")(app, db, lnb)); - app.get("/payments", auth, async (req, res) => { - const payments = await db.Payment.findAll({ - where: { user_id: req.user.id }, - order: [['id', 'DESC']], - }); - - emit(req.user.username, "payments", payments); - res.send(payments); - }); -}; diff --git a/queryRoutes.js b/queryRoutes.js deleted file mode 100644 index 1811f7c4..00000000 --- a/queryRoutes.js +++ /dev/null @@ -1,13 +0,0 @@ -const bolt11 = require("bolt11"); -const config = require("./config"); -const l = require("pino")(); - -module.exports = lna => async (req, res) => { - let hash = req.body.payreq; - let payreq = bolt11.decode(hash); - lna.queryRoutes( - { pub_key: payreq.payeeNodeKey, amt: payreq.satoshis }, - (err, response) => res.send(response) - - ); -}; diff --git a/rates.js b/rates.js deleted file mode 100644 index eda6dec4..00000000 --- a/rates.js +++ /dev/null @@ -1,42 +0,0 @@ -const axios = require("axios"); -const config = require("./config"); -const l = require("pino")(); - -module.exports = (app, socket) => { - let binance = require("./binance")(app, socket); - - let fetchRates; - (fetchRates = async () => { - let rates = require("./devrates"); - - if (process.env.NODE_ENV === "production") { - try { - let res = await axios.get( - `http://data.fixer.io/api/latest?access_key=${config.fixer}` - ); - - let fx = res.data.rates; - - fx && - Object.keys(fx).map(symbol => { - rates[symbol] = fx[symbol] / fx["USD"]; - }); - } catch (e) { - l.error("error fetching rates", e); - } - } - - app.set("fxrates", rates); - - setTimeout(fetchRates, 7200000); - })(); - - setInterval(() => { - socket.emit("rates", app.get("rates")); - }, 1000); - - setInterval(() => { - binance.terminate(); - binance = require("./binance")(app, socket); - }, 360000); -}; diff --git a/estimateBitcoinFee.js b/routes/bitcoin/fee.js index b32a0dc6..259f2329 100644 --- a/estimateBitcoinFee.js +++ b/routes/bitcoin/fee.js @@ -1,9 +1,4 @@ -const config = require("./config"); - -const l = require("pino")(); -const SATS = 100000000; - -module.exports = (app, bc, db, emit) => async (req, res) => { +module.exports = async (req, res) => { let { user } = req; let { address, amount, feeRate } = req.body; let tx, fee; @@ -24,7 +19,7 @@ module.exports = (app, bc, db, emit) => async (req, res) => { tx = await bc.fundRawTransaction(tx, params); res.send({ tx }); } catch (e) { - l.error(e); + l.error("bitcoin fee estimation error", e); return res.status(500).send(e.message); } }; diff --git a/routes/bitcoin/receive.js b/routes/bitcoin/receive.js new file mode 100644 index 00000000..b5ab4298 --- /dev/null +++ b/routes/bitcoin/receive.js @@ -0,0 +1,143 @@ +const reverse = require("buffer-reverse"); +const zmq = require("zeromq"); +const { Op } = require("sequelize"); + +const bitcoin = require("bitcoinjs-lib"); + +const zmqRawBlock = zmq.socket("sub"); +zmqRawBlock.connect(config.bitcoin.zmqrawblock); +zmqRawBlock.subscribe("rawblock"); + +const zmqRawTx = zmq.socket("sub"); +zmqRawTx.connect(config.bitcoin.zmqrawtx); +zmqRawTx.subscribe("rawtx"); + +let NETWORK = bitcoin.networks[config.bitcoin.network === "mainnet" ? "bitcoin" : config.bitcoin.network]; + +zmqRawTx.on("message", async (topic, message, sequence) => { + const hex = message.toString("hex"); + let tx = bitcoin.Transaction.fromHex(message); + let hash = reverse(tx.getHash()).toString("hex"); + + if (payments.includes(hash)) return; + + Promise.all( + tx.outs.map(async o => { + const { value } = o; + + let address; + try { + address = bitcoin.address.fromOutputScript( + o.script, + NETWORK + ); + } catch (e) { + return; + } + + if (Object.keys(addresses).includes(address)) { + payments.push(hash); + + let user = await db.User.findOne({ + where: { + username: addresses[address] + } + }); + + let invoices = await db.Payment.findAll({ + limit: 1, + where: { + address, + received: null, + amount: { + [Op.gt]: 0 + } + }, + order: [["createdAt", "DESC"]] + }); + + let tip = null; + if (invoices.length) tip = invoices[0].tip; + + let confirmed = false; + + user.address = await bc.getNewAddress("", "bech32"); + user.pending += value; + + await user.save(); + emit(user.username, "user", user); + + addresses[user.address] = user.username; + + const payment = await db.Payment.create({ + user_id: user.id, + hash, + amount: value, + currency: user.currency, + rate: app.get("rates")[user.currency], + received: true, + tip, + confirmed, + address, + asset: 'BTC', + }); + + l.info("bitcoin detected", user.username, o.value); + emit(user.username, "payment", payment); + } + }) + ); +}); + +let queue = {}; + +zmqRawBlock.on("message", async (topic, message, sequence) => { + const payments = await db.Payment.findAll({ + where: { confirmed: false } + }); + + const hashes = payments.map(p => p.hash); + + let block = bitcoin.Block.fromHex(message.toString("hex")); + block.transactions.map(tx => { + let hash = reverse(tx.getHash()).toString("hex"); + if (hashes.includes(hash)) queue[hash] = 1; + }); +}); + +setInterval(async () => { + let arr = Object.keys(queue); + for (let i = 0; i < arr.length; i++) { + let hash = arr[i]; + + let p = await db.Payment.findOne({ + include: [{ model: db.User, as: "user" }], + where: { hash, confirmed: 0 } + }) + + p.confirmed = 1; + + let user = await p.getUser(); + user.balance += p.amount; + user.pending -= Math.min(user.pending, p.amount); + emit(user.username, "user", user); + + await user.save(); + await p.save(); + + let payments = await db.Payment.findAll({ + where: { + user_id: user.id, + received: { + [Op.ne]: null + }, + }, + order: [['id', 'DESC']], + limit: 12 + }); + + l.info("bitcoin confirmed", user.username, p.amount); + emit(user.username, "payments", payments); + delete queue[hash]; + } +}, 1000); diff --git a/sendCoins.js b/routes/bitcoin/send.js index 1c9f6937..3c545178 100644 --- a/sendCoins.js +++ b/routes/bitcoin/send.js @@ -1,11 +1,7 @@ const bitcoin = require("bitcoinjs-lib"); -const config = require("./config"); const reverse = require("buffer-reverse"); -const l = require("pino")(); -const toSats = n => parseInt((n * 100000000).toFixed()) - -module.exports = (addresses, app, bc, db, emit) => async (req, res) => { +module.exports = async (req, res) => { let { user } = req; let { address, tx } = req.body; let { hex } = tx; @@ -55,7 +51,7 @@ module.exports = (addresses, app, bc, db, emit) => async (req, res) => { await user.save({ transaction }); }); } catch (e) { - l.error(e); + l.warn("insufficient funds for bitcoin payment", e); return res.status(500).send("Not enough satoshis"); } @@ -91,7 +87,7 @@ module.exports = (addresses, app, bc, db, emit) => async (req, res) => { res.send(payment); }); } catch (e) { - l.error(e); + l.error("error sending bitcoin", e); return res.status(500).send(e.message); } }; diff --git a/addInvoice.js b/routes/lightning/invoice.js index fa183321..59f0df47 100644 --- a/addInvoice.js +++ b/routes/lightning/invoice.js @@ -1,4 +1,4 @@ -module.exports = (app, db, lnb) => async (req, res) => { +module.exports = async (req, res) => { let err = m => res.status(500).send(m); let { amount, address, tip } = req.body; diff --git a/routes/lightning/query.js b/routes/lightning/query.js new file mode 100644 index 00000000..8e25293c --- /dev/null +++ b/routes/lightning/query.js @@ -0,0 +1,7 @@ +const bolt11 = require("bolt11"); + +module.exports = async (req, res) => { + let hash = req.body.payreq; + let payreq = bolt11.decode(hash); + res.send(await lna.queryRoutes({ pub_key: payreq.payeeNodeKey, amt: payreq.satoshis })); +}; diff --git a/lightningPayments.js b/routes/lightning/receive.js index 096655bf..6befc7a3 100644 --- a/lightningPayments.js +++ b/routes/lightning/receive.js @@ -1,6 +1,4 @@ -const l = require("pino")(); - -module.exports = (app, db, lna, lnb, emit, payments) => { +module.exports = { const handlePayment = async msg => { if (!msg.settled) return; diff --git a/routes/lightning/send.js b/routes/lightning/send.js new file mode 100644 index 00000000..2b661b60 --- /dev/null +++ b/routes/lightning/send.js @@ -0,0 +1,93 @@ +const bolt11 = require("bolt11"); + +module.exports = async (req, res) => { + let hash = req.body.payreq; + let payreq = bolt11.decode(hash); + let { route } = req.body; + let { user } = req; + + if (seen.includes(hash)) { + l.warn("attempted to pay a paid invoice", user.username); + 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: user.username + } + }, + { transaction } + ); + + if (balance < payreq.satoshis) { + throw new Error("insufficient funds"); + } + + user.balance -= payreq.satoshis; + await user.save({ transaction }); + }); + } catch (e) { + l.warn("insufficient funds for lightning payment", e); + return res.status(500).send("Not enough satoshis"); + } + + let m; + try { + let paymentHash = payreq.tags.find(t => t.tagName === 'payment_hash').data; + m = await lna.sendToRouteSync({ + "payment_hash": Buffer.from(paymentHash, 'hex'), + route + }); + + if (m.payment_error) return res.status(500).send(m.payment_error); + if (seen.includes(m.payment_preimage)) return; + seen.push(m.payment_preimage); + + let total = parseInt(m.payment_route.total_amt); + let fee = m.payment_route.total_fees; + + user.balance -= total - payreq.satoshis; + + await db.transaction(async transaction => { + await user.save({ transaction }); + + const payment = await db.Payment.create( + { + amount: -total, + fee, + user_id: user.id, + hash, + rate: app.get("rates")[user.currency], + currency: user.currency, + confirmed: true, + asset: 'LNBTC', + }, + { transaction } + ); + + l.info("sent lightning", user.username, -payment.amount); + emit(user.username, "payment", payment); + emit(user.username, "user", user); + + if (payreq.payeeNodeKey === config.lnb.id) { + lna.addInvoice({ value: payreq.satoshis }, (err, invoice) => { + 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(payment); + }); + } catch (e) { + l.info("error sending lightning", e); + return res.status(500).send(e); + } +}; diff --git a/payUser.js b/routes/lightning/user.js index 676b0093..c361600c 100644 --- a/payUser.js +++ b/routes/lightning/user.js @@ -1,6 +1,6 @@ const bolt11 = require("bolt11"); -module.exports = (app, db, lna, lnb) => async (req, res) => { +module.exports = async (req, res) => { let { payuser, amount } = req.body; let user = await db.User.findOne({ diff --git a/estimateLiquidFee.js b/routes/liquid/fee.js index fb9e54f0..402c0b2a 100644 --- a/estimateLiquidFee.js +++ b/routes/liquid/fee.js @@ -1,12 +1,4 @@ -const config = require("./config"); -const BitcoinCore = require("bitcoin-core"); - -const l = require("pino")(); -const SATS = 100000000; - -const bc = new BitcoinCore(config.liquid); - -module.exports = (app, db, emit) => async (req, res) => { +module.exports = async (req, res) => { let { user } = req; let { address, amount, feeRate } = req.body; let tx, fee; @@ -14,7 +6,7 @@ module.exports = (app, db, emit) => async (req, res) => { try { amount = parseInt(amount); - tx = await bc.createRawTransaction([], { + tx = await lq.createRawTransaction([], { [address]: (amount / SATS).toFixed(8) }); @@ -24,10 +16,10 @@ module.exports = (app, db, emit) => async (req, res) => { if (feeRate) params["feeRate"] = (parseInt(feeRate) / SATS * 1000).toFixed(8); - tx = await bc.fundRawTransaction(tx, params); + tx = await lq.fundRawTransaction(tx, params); res.send({ tx }); } catch (e) { - l.error(e); + l.error("error estimating liquid fee", e); return res.status(500).send(e.message); } }; diff --git a/routes/liquid/receive.js b/routes/liquid/receive.js new file mode 100644 index 00000000..2e622aad --- /dev/null +++ b/routes/liquid/receive.js @@ -0,0 +1,137 @@ +const reverse = require("buffer-reverse"); +const zmq = require("zeromq"); +const { Op } = require("sequelize"); +const bitcoin = require("elementsjs-lib"); + +const zmqRawBlock = zmq.socket("sub"); +zmqRawBlock.connect(config.liquid.zmqrawblock); +zmqRawBlock.subscribe("rawblock"); + +const zmqRawTx = zmq.socket("sub"); +zmqRawTx.connect("tcp://127.0.0.1:18603"); +zmqRawTx.subscribe("rawtx"); + +let NETWORK = bitcoin.networks[config.bitcoin.network === "mainnet" ? "bitcoin" : config.bitcoin.network]; + +zmqRawTx.on("message", async (topic, message, sequence) => { + const hex = message.toString("hex"); + const unblinded = await lq.unblindRawTransaction(hex); + const tx = await lq.decodeRawTransaction(unblinded.hex); + const blinded = await lq.decodeRawTransaction(hex); + if (payments.includes(blinded.txid)) return; + + Promise.all( + tx.vout.map(async o => { + if (!(o.scriptPubKey && o.scriptPubKey.addresses)) return; + const value = toSats(o.value); + const address = o.scriptPubKey.addresses[0]; + + if (Object.keys(addresses).includes(address)) { + let user = await db.User.findOne({ + where: { + username: addresses[address] + } + }); + + let invoices = await db.Payment.findAll({ + limit: 1, + where: { + address, + received: null, + amount: { + [Op.gt]: 0 + } + }, + order: [["id", "DESC"]] + }); + + let tip = null; + if (invoices.length) tip = invoices[0].tip; + + let confirmed = 0; + + user.pending += value; + user.confidential = await lq.getNewAddress(); + user.liquid = (await lq.getAddressInfo(user.confidential)).unconfidential; + + await user.save(); + emit(user.username, "user", user); + + addresses[user.liquid] = user.username; + + const payment = await db.Payment.create({ + user_id: user.id, + hash: blinded.txid, + amount: value, + currency: user.currency, + rate: app.get("rates")[user.currency], + received: true, + tip, + confirmed, + address, + asset: 'LBTC', + }); + payments.push(blinded.txid); + + l.info("liquid detected", user.username, value); + emit(user.username, "payment", payment); + } + }) + ); +}); + +let queue = {}; + +zmqRawBlock.on("message", async (topic, message, sequence) => { + const payments = await db.Payment.findAll({ + where: { confirmed: 0 } + }); + + const hashes = payments.map(p => p.hash); + + const block = bitcoin.Block.fromHex(message.toString("hex"), true); + const hash = await lq.getBlockHash(block.height); + const json = await lq.getBlock(hash, 2); + + json.tx.map(tx => { + if (hashes.includes(tx.txid)) queue[tx.txid] = 1; + }); +}); + +setInterval(async () => { + let arr = Object.keys(queue); + + for (let i = 0; i < arr.length; i++) { + let hash = arr[i]; + + let p = await db.Payment.findOne({ + include: [{ model: db.User, as: "user" }], + where: { hash, confirmed: 0, received: 1 } + }) + + p.confirmed = 1; + + let user = await p.getUser(); + user.balance += p.amount; + user.pending -= Math.min(user.pending, p.amount); + l.info("liquid confirmed", user.username, p.amount); + emit(user.username, "user", user); + + await user.save(); + await p.save(); + + let payments = await db.Payment.findAll({ + where: { + user_id: user.id, + received: { + [Op.ne]: null + }, + }, + order: [['id', 'DESC']], + limit: 12 + }); + + emit(user.username, "payments", payments); + delete queue[hash]; + } +}, 1000); diff --git a/sendLiquid.js b/routes/liquid/send.js index a86395a2..aaced4a9 100644 --- a/sendLiquid.js +++ b/routes/liquid/send.js @@ -1,14 +1,6 @@ -const bitcoin = require("bitcoinjs-lib"); -const config = require("./config"); const reverse = require("buffer-reverse"); -const BitcoinCore = require("bitcoin-core"); -const l = require("pino")(); -const toSats = n => parseInt((n * 100000000).toFixed()) - -const bc = new BitcoinCore(config.liquid); - -module.exports = (addresses, app, db, emit) => async (req, res) => { +module.exports = async (req, res) => { let { user } = req; let { address, @@ -61,7 +53,7 @@ module.exports = (addresses, app, db, emit) => async (req, res) => { await user.save({ transaction }); }); } catch (e) { - l.error("balance check failed", e); + l.warn("insufficient funds for liquid payment", e); return res.status(500).send("Not enough satoshis"); } diff --git a/routes/payments.js b/routes/payments.js new file mode 100644 index 00000000..d5038b08 --- /dev/null +++ b/routes/payments.js @@ -0,0 +1,40 @@ +const BitcoinCore = require("bitcoin-core"); +const createLnrpc = require("../lib/createLnrpc"); + +(async () => { + bc = new BitcoinCore(config.bitcoin); + lq = new BitcoinCore(config.liquid); + + lna = await createLnrpc(config.lna); + lnb = await createLnrpc(config.lnb); + + seen = []; + addresses = {}; + await db.User.findAll({ + attributes: ["username", "address", "liquid"] + }).map(u => { + addresses[u.address] = u.username; + if (u.liquid) addresses[u.liquid] = u.username; + }); + + app.post("/lightning/invoice", auth, require("./lightning/invoice")); + app.post("/lightning/query", auth, require("./lightning/query")); + app.post("/lightning/send", auth, require("./lightning/send")); + app.post("/lightning/user", auth, require("./lightning/user")); + + app.post("/bitcoin/send", auth, require("./bitcoin/send")); + app.post("/bitcoin/fee", auth, require("./bitcoin/fee")); + + app.post("/liquid/send", auth, require("./liquid/send")); + app.post("/liquid/fee", auth, require("./liquid/fee")); + + app.get("/payments", auth, async (req, res) => { + const payments = await db.Payment.findAll({ + where: { user_id: req.user.id }, + order: [["id", "DESC"]] + }); + + emit(req.user.username, "payments", payments); + res.send(payments); + }); +})(); diff --git a/routes/users.js b/routes/users.js new file mode 100644 index 00000000..4d9127e1 --- /dev/null +++ b/routes/users.js @@ -0,0 +1,328 @@ +const axios = require("axios"); +const bcrypt = require("bcrypt"); +const jwt = require("jsonwebtoken"); +const uuidv4 = require("uuid/v4"); +const fb = "https://graph.facebook.com/"; +const authenticator = require("otplib").authenticator; + +const pick = (O, ...K) => K.reduce((o, k) => ((o[k] = O[k]), o), {}); +let faucet = 1000; +const DAY = 24 * 60 * 60 * 1000; +require("../lib/whitelist"); + +const twofa = (req, res, next) => { + let { + user, + body: { token } + } = req; + if (!user.twofa || authenticator.check(token, user.otpsecret)) next(req, res); + else res.status(401).send("2fa required"); +}; + + +app.get("/liquidate", async (req, res) => { + let users = await db.User.findAll(); + for (let i = 0; i < users.length; i++) { + let user = users[i]; + if (!user.confidential) { + user.confidential = await lq.getNewAddress(); + user.liquid = (await lq.getAddressInfo( + user.confidential + )).unconfidential; + await user.save(); + } + + if (!user.otpsecret) { + user.otpsecret = authenticator.generateSecret(); + await user.save(); + } + } + res.end(); +}); + +const gift = async user => { + if (faucet > 0) { + user.balance = 100; + faucet -= 100; + const payment = await db.Payment.create({ + user_id: user.id, + hash: "Welcome Gift", + amount: 100, + currency: user.currency, + rate: app.get("rates")[user.currency], + received: true, + confirmed: 1, + asset: "GIFT" + }); + await user.save(); + } +}; + +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"); + + let exists = await db.User.count({ where: { username: user.username } }); + if (exists) return err("Username taken"); + + user.address = await bc.getNewAddress("", "bech32"); + user.confidential = await lq.getNewAddress(); + user.liquid = (await lq.getAddressInfo( + user.confidential + )).unconfidential; + user.name = user.username; + let ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress; + let countries = { + CA: "CAD", + US: "USD", + JP: "JPY", + CN: "CNY", + AU: "AUD", + GB: "GBP" + }; + + if (ip.startsWith('127') || ip.startsWith('192')) user.currency = "CAD"; + else { + let info = await axios.get(`http://api.ipstack.com/${ip}?access_key=${config.ipstack}`); + user.currency = countries[info.data.country_code] || "USD"; + } + + user.currencies = [user.currency]; + user.otpsecret = authenticator.generateSecret(); + + addresses[user.address] = user.username; + addresses[user.liquid] = user.username; + user = await db.User.create(user); + await gift(user); + + l.info("new user", user.username); + res.send(pick(user, ...whitelist)); +}); + +app.post("/disable2fa", auth, twofa, async (req, res) => { + let { user } = req; + user.twofa = false; + await user.save(); + emit(user.username, "user", user); + emit(user.username, "otpsecret", user.otpsecret); + l.info("disabled 2fa", user.username); + res.end(); +}); + +app.get("/otpsecret", auth, twofa, async (req, res) => { + let { user } = req; + emit(user.username, "otpsecret", user.otpsecret); + res.end(); +}); + +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.status(500).send("Invalid token"); + } + } catch (e) { + l.error("error setting up 2fa", e); + } + + l.info("enabled 2fa", user.username); + res.end(); +}); + +app.post("/user", auth, async (req, res) => { + try { + let { user } = req; + let { + username, + currency, + currencies, + twofa, + pin, + password, + passconfirm + } = req.body; + + let exists = await db.User.findOne({ + where: { username } + }); + + let token; + if (user.username !== username && exists) { + return res.status(500).send("Username taken"); + } else { + user.username = username; + addresses[user.address] = username; + addresses[user.liquid] = user.username; + token = jwt.sign({ username }, config.jwt); + res.cookie("token", token, { + expires: new Date(Date.now() + 432000000) + }); + } + + user.currencies = currencies; + user.currency = currency; + if (currencies.length) { + if (!currencies.includes(currency)) user.currency = currencies[0]; + } else { + user.currency = null; + } + + user.twofa = twofa; + user.pin = pin; + + if (password && password === passconfirm) { + user.password = await bcrypt.hash(password, 1); + } + + await user.save(); + emit(user.username, "user", req.user); + res.send({ user, token }); + } catch (e) { + l.error("error updating user", e); + } +}); + +app.post("/login", async (req, res) => { + let twofa = req.body.token; + + try { + let user = await db.User.findOne({ + where: { + username: req.body.username + } + }); + + if ( + !user || + (user.password && + !(await bcrypt.compare(req.body.password, user.password))) + ) { + l.warn("invalid username or password attempt"); + return res.status(401).end(); + } + + if (user.twofa && (typeof twofa === "undefined" || !authenticator.check(twofa, user.otpsecret))) { + return res.status(401).send("2fa required"); + } + + 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 (e) { + l.error("login error", e); + res.status(401).end(); + } +}); + +app.post("/facebookLogin", async (req, res) => { + let { accessToken, userID } = req.body; + let twofa = req.body.token; + + 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 axios.get( + `${fb}/me?access_token=${accessToken}` + )).data.name; + user.address = await bc.getNewAddress("", "bech32"); + user.confidential = await lq.getNewAddress(); + user.liquid = (await lq.getAddressInfo( + user.confidential + )).unconfidential; + user.password = await bcrypt.hash(accessToken, 1); + user.balance = 0; + user.pending = 0; + let friends = (await axios.get( + `${fb}/${userID}/friends?access_token=${accessToken}` + )).data.data; + if (friends.find(f => f.id === config.facebook.specialFriend)) { + user.friend = true; + user.limit = 200; + } + await user.save(); + addresses[user.address] = user.username; + addresses[user.liquid] = user.username; + await gift(user); + l.info("new facebook user", user.name); + } + + if (user.twofa && (typeof twofa === "undefined" || !authenticator.check(twofa, user.otpsecret))) + return res.status(401).send("2fa required"); + + user.pic = (await axios.get( + `${fb}/me/picture?access_token=${accessToken}&redirect=false` + )).data.data.url; + user.fbtoken = accessToken; + await user.save(); + + 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 (e) { + l.error("error during facebook login", e); + res.status(401).end(); + } +}); + +app.get("/me", async (req, res) => { + let { + data: { + data: { url } + } + } = await axios.get( + `${fb}/me/picture?access_token=EAAEIFqWk3ZAwBAEUfxQdH3T5CBKXmU8d7jQ5OTJBJZBiU1ZAp76lO26nh57WolM4R4JoKks9BCc49s8VrlEm2Ub1GlZCEzVD9fGxzUiranXDErDmR5gDUPKX3BhCsGA649a4hmbldRwKFTsmZCGZCergm9ACspKdTZB0WgFgA9wEdemIRIXuwCygNrymmKDh0Wd8nmoT4Hj3wZDZD&redirect=false` + ); + res.send(url); +}); + +app.get("/friends", auth, async (req, res) => { + try { + const { + data: { data } + } = await axios.get( + `${fb}/${req.user.username}/friends?access_token=${req.user.fbtoken}` + ); + + const friends = await Promise.all( + data.map(async f => { + f.pic = (await axios.get( + `${fb}/${f.id}/picture?redirect=false&type=small&access_token=${ + req.user.fbtoken + }` + )).data.data.url; + return f; + }) + ); + + res.send(friends); + } catch (e) { + res + .status(500) + .send("There was a problem getting your facebook friends: " + e); + } +}); + +setInterval(() => (faucet = 2000), DAY); diff --git a/sendPayment.js b/sendPayment.js deleted file mode 100644 index 31e3f602..00000000 --- a/sendPayment.js +++ /dev/null @@ -1,95 +0,0 @@ -const bolt11 = require("bolt11"); -const config = require("./config"); - -const l = require("pino")(); - -module.exports = (app, db, emit, seen, lna, lnb) => async (req, res) => { - let hash = req.body.payreq; - let payreq = bolt11.decode(hash); - let { route } = req.body; - let { user } = req; - - if (seen.includes(hash)) { - l.warn("attempted to pay a paid invoice", user.username); - 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: user.username - } - }, - { transaction } - ); - - if (balance < payreq.satoshis) { - throw new Error(); - } - - user.balance -= payreq.satoshis; - await user.save({ transaction }); - }); - } catch (e) { - return res.status(500).send("Not enough satoshis"); - } - - let m; - try { - let paymentHash = payreq.tags.find(t => t.tagName === 'payment_hash').data; - m = lna.sendToRouteSync({ - "payment_hash": Buffer.from(paymentHash, 'hex'), - route - }, async function(err, m) { - if (m.payment_error) return res.status(500).send(m.payment_error); - if (seen.includes(m.payment_preimage)) return; - seen.push(m.payment_preimage); - - let total = parseInt(m.payment_route.total_amt); - let fee = m.payment_route.total_fees; - - user.balance -= total - payreq.satoshis; - - await db.transaction(async transaction => { - await user.save({ transaction }); - - const payment = await db.Payment.create( - { - amount: -total, - fee, - user_id: user.id, - hash, - rate: app.get("rates")[user.currency], - currency: user.currency, - confirmed: true, - asset: 'LNBTC', - }, - { transaction } - ); - - l.info("sent lightning", user.username, -payment.amount); - emit(user.username, "payment", payment); - emit(user.username, "user", user); - - if (payreq.payeeNodeKey === config.lnb.id) { - lna.addInvoice({ value: payreq.satoshis }, (err, invoice) => { - 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(payment); - }); - }); - } catch (e) { - l.info(e); - return res.status(500).send(e); - } -}; diff --git a/sockets.js b/sockets.js deleted file mode 100644 index 9f9681a6..00000000 --- a/sockets.js +++ /dev/null @@ -1,74 +0,0 @@ -const jwt = require("jsonwebtoken"); -const io = require("socket.io"); -const whitelist = require("./whitelist"); -const Sequelize = require("sequelize"); - -/* eslint-disable-next-line */ -const pick = (O, ...K) => K.reduce((o, k) => ((o[k] = O[k]), o), {}); -const l = require("pino")();; - -module.exports = (app, db, server) => { - const socket = io(server, { origins: "*:*" }); - const sids = {}; - - const emit = (username, msg, data) => { - if (data.username !== undefined) data = pick(data, ...whitelist); - - if (!sids[username]) return; - sids[username].map((sid, i) => { - try { - socket.to(sid).emit(msg, data); - } catch (e) { - sids[username].splice(i, 1); - } - }); - }; - - socket.use((socket, next) => { - try { - let token = socket.handshake.query.token; - let user = jwt.decode(token).username; - socket.request.user = user; - sids[user] ? sids[user].push(socket.id) : (sids[user] = [socket.id]); - sids[socket.id] = user; - } catch (e) { - l.error("error creating socket", e); - } - next(); - }); - - socket.sockets.on("connect", async socket => { - socket.emit("connected"); - if (app.get("rates")) socket.emit("rate", app.get("rates").ask); - socket.on("getuser", async (data, callback) => { - l.info(`logging in ${socket.request.user}`); - const user = await db.User.findOne({ - include: [ - { - model: db.Payment, - as: "payments", - order: [["id", "DESC"]], - received: { - [Sequelize.Op.ne]: null - }, - limit: 12 - } - ], - where: { - username: socket.request.user - } - }); - - callback(user); - }); - - socket.on("disconnect", s => { - let user = sids[socket.id]; - l.info("logging out", user); - sids[user].splice(sids[user].indexOf(socket.id), 1); - delete sids[socket.id]; - }); - }); - - return [socket, emit]; -}; diff --git a/users.js b/users.js deleted file mode 100644 index 2cc43361..00000000 --- a/users.js +++ /dev/null @@ -1,476 +0,0 @@ -const axios = require("axios"); -const bcrypt = require("bcrypt"); -const jwt = require("jsonwebtoken"); -const mailgun = require("mailgun-js"); -const uuidv4 = require("uuid/v4"); -const BitcoinCore = require("bitcoin-core"); -const config = require("./config"); -const whitelist = require("./whitelist"); -const fb = "https://graph.facebook.com/"; -const liquid = new BitcoinCore(config.liquid); -const Sequelize = require("sequelize"); -const authenticator = require("otplib").authenticator; - -const pick = (O, ...K) => K.reduce((o, k) => ((o[k] = O[k]), o), {}); -const l = require("pino")(); -let faucet = 1000; -const DAY = 24 * 60 * 60 * 1000; - -const twofa = (req, res, next) => { - let { - user, - body: { token } - } = req; - if (!user.twofa || authenticator.check(token, user.otpsecret)) next(req, res); - else res.status(401).send("2fa required"); -}; - -module.exports = (addresses, auth, app, bc, db, emit) => { - app.get("/liquidate", async (req, res) => { - let users = await db.User.findAll(); - for (let i = 0; i < users.length; i++) { - let user = users[i]; - if (!user.confidential) { - user.confidential = await liquid.getNewAddress(); - user.liquid = (await liquid.getAddressInfo( - user.confidential - )).unconfidential; - await user.save(); - } - - if (!user.otpsecret) { - user.otpsecret = authenticator.generateSecret(); - await user.save(); - } - } - res.end(); - }); - - const gift = async user => { - if (faucet > 0) { - user.balance = 100; - faucet -= 100; - const payment = await db.Payment.create({ - user_id: user.id, - hash: "Welcome Gift", - amount: 100, - currency: user.currency, - rate: app.get("rates")[user.currency], - received: true, - confirmed: 1, - asset: "GIFT" - }); - await user.save(); - } - }; - - 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"); - - let exists = await db.User.count({ where: { username: user.username } }); - if (exists) return err("Username taken"); - - user.address = await bc.getNewAddress("", "bech32"); - user.confidential = await liquid.getNewAddress(); - user.liquid = (await liquid.getAddressInfo( - user.confidential - )).unconfidential; - user.name = user.username; - let ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress; - let countries = { - CA: "CAD", - US: "USD", - JP: "JPY", - CN: "CNY", - AU: "AUD", - GB: "GBP" - }; - - if (ip.startsWith('127') || ip.startsWith('192')) user.currency = "CAD"; - else { - let info = await axios.get(`http://api.ipstack.com/${ip}?access_key=${config.ipstack}`); - user.currency = countries[info.data.country_code] || "USD"; - } - - user.currencies = [user.currency]; - user.otpsecret = authenticator.generateSecret(); - - addresses[user.address] = user.username; - addresses[user.liquid] = user.username; - user = await db.User.create(user); - await gift(user); - - l.info("new user", user.username); - 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/verify/${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.error(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("/disable2fa", auth, twofa, async (req, res) => { - let { user } = req; - user.twofa = false; - await user.save(); - emit(user.username, "user", user); - emit(user.username, "otpsecret", user.otpsecret); - l.info("disabled 2fa", user.username); - res.end(); - }); - - app.get("/otpsecret", auth, twofa, async (req, res) => { - let { user } = req; - emit(user.username, "otpsecret", user.otpsecret); - res.end(); - }); - - 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.status(500).send("Invalid token"); - } - } catch (e) { - l.error(e); - } - - l.info("enabled 2fa", user.username); - res.end(); - }); - - app.post("/user", auth, async (req, res) => { - try { - let { user } = req; - let { - username, - currency, - currencies, - email, - phone, - twofa, - pin, - password, - passconfirm - } = req.body; - - let exists = await db.User.findOne({ - where: { username } - }); - - let token; - if (user.username !== username && exists) { - return res.status(500).send("Username taken"); - } else { - user.username = username; - addresses[user.address] = username; - addresses[user.liquid] = user.username; - token = jwt.sign({ username }, config.jwt); - res.cookie("token", token, { - expires: new Date(Date.now() + 432000000) - }); - } - - if (email && user.email !== email) { - if (!require("email-validator").validate(email)) { - return res.status(500).send("invalid email"); - } - - user.email = email; - user.emailVerified = false; - requestEmail(user); - } - - if (user.phone !== phone) { - user.phone = phone; - user.phoneVerified = false; - requestPhone(user); - } - - user.currencies = currencies; - user.currency = currency; - if (currencies.length) { - if (!currencies.includes(currency)) user.currency = currencies[0]; - } else { - user.currency = null; - } - - user.email = email; - user.phone = phone; - user.twofa = twofa; - user.pin = pin; - - if (password && password === passconfirm) { - user.password = await bcrypt.hash(password, 1); - } - - await user.save(); - emit(user.username, "user", req.user); - res.send({ user, token }); - } catch (e) { - l.error(e); - } - }); - - app.post("/forgot", async (req, res) => { - let user = await db.User.findOne({ - where: { - email: req.body.email - } - }); - - if (user) { - 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.error(e); - } - } - - res.end(); - }); - - app.get("/verifyEmail/:username/:token", auth, async (req, res) => { - let user = await db.User.findOne({ - where: { - username: req.params.username, - emailToken: req.params.token - } - }); - - user.emailToken = uuidv4(); - - 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 - } - }); - - 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("/login", async (req, res) => { - let twofa = req.body.token; - - try { - let user = await db.User.findOne({ - where: { - username: req.body.username - } - }); - - if ( - !user || - (user.password && - !(await bcrypt.compare(req.body.password, user.password))) - ) { - return res.status(401).end(); - } - - if (user.twofa && (typeof twofa === "undefined" || !authenticator.check(twofa, user.otpsecret))) - return res.status(401).send("2fa required"); - - 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 (e) { - l.error(e); - res.status(401).end(); - } - }); - - app.post("/facebookLogin", async (req, res) => { - let { accessToken, userID } = req.body; - let twofa = req.body.token; - - 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 axios.get( - `${fb}/me?access_token=${accessToken}` - )).data.name; - user.address = await bc.getNewAddress("", "bech32"); - user.confidential = await liquid.getNewAddress(); - user.liquid = (await liquid.getAddressInfo( - user.confidential - )).unconfidential; - user.password = await bcrypt.hash(accessToken, 1); - user.balance = 0; - user.pending = 0; - let friends = (await axios.get( - `${fb}/${userID}/friends?access_token=${accessToken}` - )).data.data; - if (friends.find(f => f.id === config.facebook.specialFriend)) { - user.friend = true; - user.limit = 200; - } - await user.save(); - addresses[user.address] = user.username; - addresses[user.liquid] = user.username; - await gift(user); - l.info("new facebook user", user.name); - } - - if (user.twofa && (typeof twofa === "undefined" || !authenticator.check(twofa, user.otpsecret))) - return res.status(401).send("2fa required"); - - user.pic = (await axios.get( - `${fb}/me/picture?access_token=${accessToken}&redirect=false` - )).data.data.url; - user.fbtoken = accessToken; - await user.save(); - - 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 (e) { - l.error(e); - res.status(401).end(); - } - }); - - app.get("/me", async (req, res) => { - let { - data: { - data: { url } - } - } = await axios.get( - `${fb}/me/picture?access_token=EAAEIFqWk3ZAwBAEUfxQdH3T5CBKXmU8d7jQ5OTJBJZBiU1ZAp76lO26nh57WolM4R4JoKks9BCc49s8VrlEm2Ub1GlZCEzVD9fGxzUiranXDErDmR5gDUPKX3BhCsGA649a4hmbldRwKFTsmZCGZCergm9ACspKdTZB0WgFgA9wEdemIRIXuwCygNrymmKDh0Wd8nmoT4Hj3wZDZD&redirect=false` - ); - res.send(url); - }); - - app.get("/friends", auth, async (req, res) => { - try { - const { - data: { data } - } = await axios.get( - `${fb}/${req.user.username}/friends?access_token=${req.user.fbtoken}` - ); - - const friends = await Promise.all( - data.map(async f => { - f.pic = (await axios.get( - `${fb}/${f.id}/picture?redirect=false&type=small&access_token=${ - req.user.fbtoken - }` - )).data.data.url; - return f; - }) - ); - - res.send(friends); - } catch (e) { - res - .status(500) - .send("There was a problem getting your facebook friends: " + e); - } - }); - - setInterval(() => (faucet = 2000), DAY); -}; |
