diff options
| author | Adam Soltys <[email protected]> | 2020-07-29 16:40:30 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2020-07-29 16:40:30 -0700 |
| commit | 99d71f034cfb7c1675263539fa6aecb17429d134 (patch) | |
| tree | 6d29806937436fde454f357a8dd5f7a30fa6b8bd /routes | |
| parent | ffee4c7051982f3677d6717d24e7b357a89802eb (diff) | |
| download | coinos-server-99d71f034cfb7c1675263539fa6aecb17429d134.tar.gz coinos-server-99d71f034cfb7c1675263539fa6aecb17429d134.zip | |
redeem codes
Diffstat (limited to 'routes')
| -rw-r--r-- | routes/payments.js | 34 | ||||
| -rw-r--r-- | routes/send.js | 165 | ||||
| -rw-r--r-- | routes/swaps.js | 43 | ||||
| -rw-r--r-- | routes/users.js | 654 |
4 files changed, 512 insertions, 384 deletions
diff --git a/routes/payments.js b/routes/payments.js index 20a56baf..0a1ff27e 100644 --- a/routes/payments.js +++ b/routes/payments.js @@ -5,7 +5,7 @@ const { join } = require("path"); const fs = require("fs"); const read = require("../lib/read"); -(async () => { +(ah(async () => { seen = []; addresses = {}; issuances = {}; @@ -121,7 +121,7 @@ const read = require("../lib/read"); app.get( "/payments", auth, - ah(async (req, res, next) => { + ah(async (req, res) => { let payments = await req.user.getPayments({ where: { account_id: req.user.account_id, @@ -136,4 +136,32 @@ const read = require("../lib/read"); res.send(payments); }) ); -})(); + + app.get( + "/payment/:redeemcode", + ah(async (req, res) => { + try { + const { redeemcode } = req.params; + l.info("getting payment", redeemcode); + let payment = await db.Payment.findOne({ + where: { + redeemcode, + }, + include: { + model: db.Account, + as: "account", + }, + }); + + l.info("oyyy"); + if (!payment) fail("invalid code"); + + l.info("got payment", payment); + + res.send(payment); + } catch(e) { + res.status(500).send(e.message); + } + }) + ); +}))(); diff --git a/routes/send.js b/routes/send.js index 00943d29..8ffff4dd 100644 --- a/routes/send.js +++ b/routes/send.js @@ -1,4 +1,5 @@ const axios = require("axios"); +const uuidv4 = require("uuid/v4"); module.exports = ah(async (req, res, next) => { let { amount, asset, memo, username } = req.body; @@ -9,14 +10,14 @@ module.exports = ah(async (req, res, next) => { return res.status(500).send("Amount must be greater than zero"); try { - await db.transaction(async transaction => { + await db.transaction(async (transaction) => { let account = await db.Account.findOne({ where: { user_id: user.id, - asset + asset, }, lock: transaction.LOCK.UPDATE, - transaction + transaction, }); if (account.balance < amount) { @@ -28,20 +29,24 @@ module.exports = ah(async (req, res, next) => { account.balance -= amount; await account.save({ transaction }); - let payment = await db.Payment.create( - { - amount: -amount, - account_id: account.id, - memo, - user_id: user.id, - rate: app.get("rates")[user.currency], - currency: user.currency, - confirmed: true, - hash: "Internal Transfer", - network: "COINOS" - }, - { transaction } - ); + const params = { + amount: -amount, + account_id: account.id, + memo, + user_id: user.id, + rate: app.get("rates")[user.currency], + currency: user.currency, + confirmed: true, + hash: "Internal Transfer", + network: "COINOS", + }; + + if (!username) { + l.info("creating redeemable payment"); + params.redeemcode = uuidv4(); + } + + let payment = await db.Payment.create(params, { transaction }); payment = payment.get({ plain: true }); payment.account = account.get({ plain: true }); @@ -53,80 +58,82 @@ module.exports = ah(async (req, res, next) => { emit(user.username, "user", user); res.send(payment); - user = await db.User.findOne({ - where: { username } - }); + if (username) { + user = await db.User.findOne({ + where: { username }, + }); - let params = { - user_id: user.id, - asset - }; + let params = { + user_id: user.id, + asset, + }; - account = await db.Account.findOne({ - where: params, - lock: transaction.LOCK.UPDATE, - transaction - }); + account = await db.Account.findOne({ + where: params, + lock: transaction.LOCK.UPDATE, + transaction, + }); - if (account) { - account.balance += amount; - await account.save({ transaction }); - } else { - let name = asset.substr(0, 6); - let domain; - let ticker = asset.substr(0, 3).toUpperCase(); - let precision = 8; + if (account) { + account.balance += amount; + await account.save({ transaction }); + } else { + let name = asset.substr(0, 6); + let domain; + let ticker = asset.substr(0, 3).toUpperCase(); + let precision = 8; - const assets = (await axios.get("https://assets.blockstream.info/")) - .data; + const assets = (await axios.get("https://assets.blockstream.info/")) + .data; - if (assets[asset]) { - ({ domain, ticker, precision, name } = assets[asset]); - } else { - const existing = await db.Account.findOne({ - where: { - asset - }, - order: [["id", "ASC"]], - limit: 1 - }); + if (assets[asset]) { + ({ domain, ticker, precision, name } = assets[asset]); + } else { + const existing = await db.Account.findOne({ + where: { + asset, + }, + order: [["id", "ASC"]], + limit: 1, + }); - l.info("existing", existing); + l.info("existing", existing); - if (existing) { - ({ domain, ticker, precision, name } = existing); + if (existing) { + ({ domain, ticker, precision, name } = existing); + } } - } - params = { ...params, ...{ domain, ticker, precision, name } }; - params.balance = amount; - params.pending = 0; - account = await db.Account.create(params, { transaction }); - } + params = { ...params, ...{ domain, ticker, precision, name } }; + params.balance = amount; + params.pending = 0; + account = await db.Account.create(params, { transaction }); + } - payment = await db.Payment.create( - { - amount, - account_id: account.id, - user_id: user.id, - rate: app.get("rates")[user.currency], - currency: user.currency, - confirmed: true, - hash: "Internal Transfer", - memo, - network: "COINOS", - received: true - }, - { transaction } - ); + payment = await db.Payment.create( + { + amount, + account_id: account.id, + user_id: user.id, + rate: app.get("rates")[user.currency], + currency: user.currency, + confirmed: true, + hash: "Internal Transfer", + memo, + network: "COINOS", + received: true, + }, + { transaction } + ); - payment = payment.get({ plain: true }); - payment.account = account.get({ plain: true }); - emit(user.username, "account", account); - emit(user.username, "payment", payment); + payment = payment.get({ plain: true }); + payment.account = account.get({ plain: true }); + emit(user.username, "account", account); + emit(user.username, "payment", payment); - l.info("received internal", user.username, amount); - notify(user, `Received ${amount} SAT`); + l.info("received internal", user.username, amount); + notify(user, `Received ${amount} SAT`); + } }); } catch (e) { l.error( diff --git a/routes/swaps.js b/routes/swaps.js index 0d88f3bf..203b6602 100644 --- a/routes/swaps.js +++ b/routes/swaps.js @@ -4,6 +4,7 @@ const level = require("level"); const fs = require("fs"); const { spawn } = require("child_process"); const leveldb = level("leveldb"); +const getAccount = require("../lib/account"); const assets = { b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23: "bitcoin", @@ -538,45 +539,3 @@ app.post("/publish", auth, ah(async (req, res) => { } })); -const getAccount = async (asset, user) => { - const account = await db.Account.findOne({ - where: { - user_id: user.id, - asset, - }, - }); - - if (account) return account; - - let params = { - user_id: user.id, - asset, - }; - let name = asset.substr(0, 6); - let domain = ''; - let ticker = asset.substr(0, 3).toUpperCase(); - let precision = 8; - - const assets = (await axios.get("https://assets.blockstream.info/")).data; - - if (assets[asset]) { - ({ domain, ticker, precision, name } = assets[asset]); - } else { - const existing = await db.Account.findOne({ - where: { - asset, - }, - order: [["id", "ASC"]], - limit: 1, - }); - - if (existing) { - ({ domain, ticker, precision, name } = existing); - } - } - - params = { ...params, ...{ domain, ticker, precision, name } }; - params.balance = 0; - params.pending = 0; - return db.Account.create(params); -}; diff --git a/routes/users.js b/routes/users.js index eaf10d72..81ecf817 100644 --- a/routes/users.js +++ b/routes/users.js @@ -4,11 +4,12 @@ const jwt = require("jsonwebtoken"); const authenticator = require("otplib").authenticator; const textToImage = require("text-to-image"); const randomWord = require("random-words"); +const getAccount = require("../lib/account"); const pick = (O, ...K) => K.reduce((o, k) => ((o[k] = O[k]), o), {}); require("../lib/whitelist"); -const twofa = (req, res, next) => { +const twofa = ah((req, res, next) => { let { user, body: { token }, @@ -20,321 +21,454 @@ const twofa = (req, res, next) => { ) { return res.status(401).send("2fa required"); } else next(); -}; - -app.get("/users/:username", async (req, res) => { - const { username } = req.params; - const user = await db.User.findOne({ - attributes: ["username"], - where: { username }, - }); - if (user) res.send(user); - else res.status(500).send("User not found"); }); -app.get("/challenge", async (req, res) => { - const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress; - challenge[ip] = randomWord(); - l.info("setting up challenge", ip, challenge[ip]); - const data = await textToImage.generate(challenge[ip]); - res.send(data); -}); +app.get( + "/users/:username", + ah(async (req, res) => { + const { username } = req.params; + const user = await db.User.findOne({ + attributes: ["username"], + where: { username }, + }); + if (user) res.send(user); + else res.status(500).send("User not found"); + }) +); -app.post("/register", async (req, res) => { - try { +app.get( + "/challenge", + ah(async (req, res) => { const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress; - const user = await register(req.body.user, ip, true); - res.send(pick(user, ...whitelist)); - } catch (e) { - res.status(500).send(e.message); - } -}); + challenge[ip] = randomWord(); + l.info("setting up challenge", ip, challenge[ip]); + const data = await textToImage.generate(challenge[ip]); + res.send(data); + }) +); -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.post( + "/register", + ah(async (req, res) => { + try { + const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress; + const user = await register(req.body.user, ip, true); + res.send(pick(user, ...whitelist)); + } catch (e) { + res.status(500).send(e.message); + } + }) +); -app.get("/otpsecret", auth, twofa, async (req, res) => { - let { user } = req; - emit(user.username, "otpsecret", user.otpsecret); - res.end(); -}); +app.post( + "/disable2fa", + auth, + twofa, + ah(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, + ah(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"); +app.post( + "/2fa", + auth, + ah(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); } - } catch (e) { - l.error("error setting up 2fa", e); - } - l.info("enabled 2fa", user.username); - res.end(); -}); + l.info("enabled 2fa", user.username); + res.end(); + }) +); -app.post("/user", auth, async (req, res) => { - try { - let { user } = req; - let { - username, - fiat, - unit, - currency, - currencies, - twofa, - pin, - password, - passconfirm, - tokens, - seed, - } = req.body; +app.post( + "/user", + auth, + ah(async (req, res) => { + try { + let { user } = req; + let { + username, + fiat, + unit, + currency, + currencies, + twofa, + pin, + password, + passconfirm, + tokens, + seed, + } = req.body; - let exists = await db.User.findOne({ - where: { username }, - }); + let exists = await db.User.findOne({ + where: { username }, + }); - let token; - if (user.username !== username && exists) { - return res.status(500).send("Username taken"); - } else { - sockets[username] = sockets[user.username]; - user.username = username; + let token; + if (user.username !== username && exists) { + return res.status(500).send("Username taken"); + } else { + sockets[username] = sockets[user.username]; + user.username = username; - if (user.address) addresses[user.address] = username; + if (user.address) addresses[user.address] = username; - if (user.liquid) addresses[user.liquid] = user.username; + if (user.liquid) addresses[user.liquid] = user.username; - token = jwt.sign({ username }, config.jwt); - res.cookie("token", token, { - expires: new Date(Date.now() + 432000000), - }); - } + token = jwt.sign({ username }, config.jwt); + res.cookie("token", token, { + expires: new Date(Date.now() + 432000000), + }); + } + + if (unit) user.unit = unit; + user.currency = currency; + user.currencies = currencies; - if (unit) user.unit = unit; - user.currency = currency; - user.currencies = currencies; + user.tokens = tokens; + user.twofa = twofa; + user.pin = pin; + user.seed = seed; + user.fiat = fiat; - user.tokens = tokens; - user.twofa = twofa; - user.pin = pin; - user.seed = seed; - user.fiat = fiat; + if (password && password === passconfirm) { + user.password = await bcrypt.hash(password, 1); + } - if (password && password === passconfirm) { - user.password = await bcrypt.hash(password, 1); + await user.save(); + emit(user.username, "user", user); + res.send({ user, token }); + } catch (e) { + l.error("error updating user", e.message); } + }) +); - await user.save(); - emit(user.username, "user", user); - res.send({ user, token }); - } catch (e) { - l.error("error updating user", e.message); - } -}); +app.post( + "/keys", + auth, + ah(async (req, res) => { + const { key: hex } = req.body; + const key = await db.Key.create({ + user_id: req.user.id, + hex, + }); + emit(req.user.username, "key", key); + }) +); -app.post("/keys", auth, async (req, res) => { - const { key: hex } = req.body; - const key = await db.Key.create({ - user_id: req.user.id, - hex, - }); - emit(req.user.username, "key", key); -}); +app.post( + "/keys/delete", + auth, + ah(async (req, res) => { + const { hex } = req.body; + ( + await db.Key.findOne({ + where: { + user_id: req.user.id, + hex, + }, + }) + ).destroy(); + }) +); -app.post("/keys/delete", auth, async (req, res) => { - const { hex } = req.body; - ( - await db.Key.findOne({ - where: { - user_id: req.user.id, - hex, - }, - }) - ).destroy(); -}); +app.post( + "/login", + ah(async (req, res) => { + const { params, sig, key } = req.body; -app.post("/login", async (req, res) => { - const { params, sig, key } = req.body; + if (sig) { + const { callback } = params; - if (sig) { - const { callback } = params; + try { + const url = `${callback}&sig=${sig}&key=${key}`; + const response = await axios.get(url); + res.send(response.data); + } catch (e) { + l.error("problem calling lnurl login", e.message); + res.status(500).send(e.message); + } - try { - const url = `${callback}&sig=${sig}&key=${key}`; - const response = await axios.get(url); - res.send(response.data); - } catch (e) { - l.error("problem calling lnurl login", e.message); - res.status(500).send(e.message); + return; } - return; - } + let twofa = req.body.token; + l.info( + "login attempt", + req.body.username, + req.headers["x-forwarded-for"] || req.connection.remoteAddress + ); + + try { + let user = await getUser(req.body.username); - let twofa = req.body.token; - l.info( - "login attempt", - req.body.username, - req.headers["x-forwarded-for"] || req.connection.remoteAddress - ); + if ( + !user || + (user.password && + !(await bcrypt.compare(req.body.password, user.password))) + ) { + l.warn( + "invalid username or password attempt", + req.body.username, + req.body.password + ); + return res.status(401).end(); + } - try { - let user = await getUser(req.body.username); + if ( + user.twofa && + (typeof twofa === "undefined" || + !authenticator.check(twofa, user.otpsecret)) + ) { + return res.status(401).send("2fa required"); + } - if ( - !user || - (user.password && - !(await bcrypt.compare(req.body.password, user.password))) - ) { - l.warn( - "invalid username or password attempt", - req.body.username, - req.body.password - ); - 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.keys = await user.getKeys(); + user = pick(user, ...whitelist); + res.send({ user, token }); + } catch (e) { + l.error("login error", e.message); + res.status(401).end(); } + }) +); - if ( - user.twofa && - (typeof twofa === "undefined" || - !authenticator.check(twofa, user.otpsecret)) - ) { - return res.status(401).send("2fa required"); +app.post( + "/logout", + auth, + ah(async (req, res) => { + let { subscription } = req.body; + const { username } = req.user; + l.info("logging out", username); + if (!subscription) return res.end(); + let i = req.user.subscriptions.findIndex( + (s) => JSON.stringify(s) === subscription + ); + if (i > -1) { + req.user.subscriptions.splice(i, 1); } + await req.user.save(); + Object.keys(logins).map( + (k) => logins[k]["username"] === username && delete logins[k] + ); - let payload = { username: user.username }; - let token = jwt.sign(payload, config.jwt); - res.cookie("token", token, { expires: new Date(Date.now() + 432000000) }); - user.keys = await user.getKeys(); - user = pick(user, ...whitelist); - res.send({ user, token }); - } catch (e) { - l.error("login error", e.message); - res.status(401).end(); - } -}); + res.end(); + }) +); -app.post("/logout", auth, async (req, res) => { - let { subscription } = req.body; - const { username } = req.user; - l.info("logging out", username); - if (!subscription) return res.end(); - let i = req.user.subscriptions.findIndex( - (s) => JSON.stringify(s) === subscription - ); - if (i > -1) { - req.user.subscriptions.splice(i, 1); - } - await req.user.save(); - Object.keys(logins).map( - (k) => logins[k]["username"] === username && delete logins[k] - ); +app.post( + "/address", + auth, + ah(async (req, res) => { + if (!config.bitcoin) return res.status(500).send("Bitcoin not configured"); - res.end(); -}); + let { user } = req; + delete addresses[user.address]; -app.post("/address", auth, async (req, res) => { - if (!config.bitcoin) return res.status(500).send("Bitcoin not configured"); + if (config.bitcoin.walletpass) + await bc.walletPassphrase(config.bitcoin.walletpass, 300); - let { user } = req; - delete addresses[user.address]; + user.address = await bc.getNewAddress("", req.body.type || "bech32"); + await user.save(); + addresses[user.address] = user.username; + emit(user.username, "user", user); + res.send(user.address); + }) +); - if (config.bitcoin.walletpass) - await bc.walletPassphrase(config.bitcoin.walletpass, 300); +app.post( + "/account", + auth, + ah(async (req, res) => { + let { user } = req; + let { asset, domain, precision, name, ticker, user_id } = req.body; - user.address = await bc.getNewAddress("", req.body.type || "bech32"); - await user.save(); - addresses[user.address] = user.username; - emit(user.username, "user", user); - res.send(user.address); -}); + try { + const account = await db.Account.findOne({ + where: { user_id, asset }, + }); -app.post("/account", auth, async (req, res) => { - let { user } = req; - let { asset, domain, precision, name, ticker, user_id } = req.body; + account.domain = domain; + account.name = name; + account.ticker = ticker; + account.precision = precision; - try { - const account = await db.Account.findOne({ - where: { user_id, asset }, - }); + await account.save(); - account.domain = domain; - account.name = name; - account.ticker = ticker; - account.precision = precision; + emit(user.username, "account", account); + res.end(); + } catch (e) { + l.error(e.message); + return res.status(500).send("There was a problem updating the account"); + } + }) +); - await account.save(); +app.post( + "/shiftAccount", + auth, + ah(async (req, res) => { + let { user } = req; + let { asset } = req.body; - emit(user.username, "account", account); - res.end(); - } catch (e) { - l.error(e.message); - return res.status(500).send("There was a problem updating the account"); - } + try { + const account = await db.Account.findOne({ + where: { user_id: user.id, asset }, + }); + + user.account_id = account.id; + await user.save(); + let payments = await user.getPayments({ + where: { + account_id: user.account_id, + }, + order: [["id", "DESC"]], + limit: 12, + include: { + model: db.Account, + as: "account", + }, + }); + user.payments = payments; + user.account = account; + + emit(user.username, "user", user); + res.end(); + } catch (e) { + l.error(e.message); + return res.status(500).send("There was a problem switching accounts"); + } + }) +); + +app.get("/vapidPublicKey", function (req, res) { + res.send(config.vapid.publicKey); }); -app.post("/shiftAccount", auth, async (req, res) => { - let { user } = req; - let { asset } = req.body; +app.post( + "/subscribe", + auth, + ah(async function (req, res) { + let { subscriptions } = req.user; + let { subscription } = req.body; + if (!subscriptions) subscriptions = []; + if ( + !subscriptions.find( + (s) => JSON.stringify(s) === JSON.stringify(subscription) + ) + ) + subscriptions.push(subscription); + req.user.subscriptions = subscriptions; + l.info("subscribing", req.user.username); + await req.user.save(); + res.sendStatus(201); + }) +); - try { - const account = await db.Account.findOne({ - where: { user_id: user.id, asset }, - }); +app.post( + "/redeem", + optionalAuth, + ah(async function (req, res) { + const { redeemcode } = req.body; + if (!redeemcode) fail("no code provided"); - user.account_id = account.id; - await user.save(); - let payments = await user.getPayments({ + let { user } = req; + + const source = await db.Payment.findOne({ where: { - account_id: user.account_id, + redeemcode: req.body.redeemcode, }, - order: [["id", "DESC"]], - limit: 12, include: { model: db.Account, as: "account", }, }); - user.payments = payments; - user.account = account; - emit(user.username, "user", user); - res.end(); - } catch (e) { - l.error(e.message); - return res.status(500).send("There was a problem switching accounts"); - } -}); + l.info("redeeming", redeemcode); -app.get("/vapidPublicKey", function (req, res) { - res.send(config.vapid.publicKey); -}); + if (!source) fail("Invalid code"); + if (source.redeemed) fail("Voucher has already been redeemed"); -app.post("/subscribe", auth, async function (req, res) { - let { subscriptions } = req.user; - let { subscription } = req.body; - if (!subscriptions) subscriptions = []; - if ( - !subscriptions.find( - (s) => JSON.stringify(s) === JSON.stringify(subscription) - ) - ) - subscriptions.push(subscription); - req.user.subscriptions = subscriptions; - l.info("subscribing", req.user.username); - await req.user.save(); - res.sendStatus(201); -}); + if (!user) { + const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress; + user = await register({ + username: redeemcode.substr(0, 8), + password: '', + } , ip, false); + + let payload = { username: user.username }; + let token = jwt.sign(payload, config.jwt); + res.cookie("token", token, { expires: new Date(Date.now() + 432000000) }); + return res.send({ user }); + } + + let account = await getAccount(source.account.asset, user); + + await db.transaction(async (transaction) => { + let { hash, memo, confirmed, fee, network } = source; + + source.redeemed = true; + await source.save(); + + let payment = await db.Payment.create( + { + amount: -source.amount, + account_id: account.id, + user_id: user.id, + hash: "Redeemed Voucher " + redeemcode, + memo, + rate: app.get("rates")[user.currency], + currency: user.currency, + confirmed, + network, + received: true, + fee, + }, + { transaction } + ); + + account.balance -= source.amount; + await account.save(); + + payment = payment.get({ plain: true }); + payment.account = account.get({ plain: true }); + emit(user.username, "payment", payment); + emit(user.username, "account", account); + + res.send({ payment }); + }); + }) +); |
