diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | index.js | 2 | ||||
| -rw-r--r-- | lib/register.js | 121 | ||||
| -rw-r--r-- | routes/lnurl.js | 53 | ||||
| -rw-r--r-- | routes/users.js | 183 |
5 files changed, 198 insertions, 162 deletions
@@ -7,3 +7,4 @@ config/clientversion yarn-error.log .pnp* .yarn* +tls.* @@ -11,6 +11,7 @@ config = require("./config"); networks = []; prod = process.env.NODE_ENV === "production"; +challenge = {}; logins = {}; sessions = {}; sockets = {}; @@ -38,6 +39,7 @@ require("./lib/sockets"); require("./lib/passport"); require("./lib/rates"); require("./lib/notifications"); +require("./lib/register"); require("./routes/assets"); require("./routes/invoices"); diff --git a/lib/register.js b/lib/register.js new file mode 100644 index 00000000..bc17496e --- /dev/null +++ b/lib/register.js @@ -0,0 +1,121 @@ +const authenticator = require("otplib").authenticator; +const axios = require("axios"); +const bcrypt = require("bcrypt"); + +const DAY = 24 * 60 * 60 * 1000; + +let faucet = 1000; +setInterval(() => (faucet = 2000), DAY); + +const gift = async user => { + const account = user.accounts[0]; + + if (faucet > 0) { + faucet -= 100; + + account.balance = 100; + await account.save(); + + const payment = await db.Payment.create({ + account_id: account.id, + user_id: user.id, + hash: "Welcome Gift", + amount: 100, + currency: user.currency, + rate: app.get("rates")[user.currency], + received: true, + confirmed: 1, + network: "GIFT" + }); + + await user.save(); + } +}; + +register = async (user, ip, requireChallenge) => { + if (!(user && user.username)) throw new Error("Username required"); + + let exists = await db.User.count({ where: { username: user.username } }); + if (exists) throw new Error(`Username ${user.username} taken`); + + if (user.password && user.password === user.confirm) { + user.password = await bcrypt.hash(user.password, 1); + } + + if (config.bitcoin) { + if (config.bitcoin.walletpass) + await bc.walletPassphrase(config.bitcoin.walletpass, 300); + + user.address = await bc.getNewAddress("", "bech32"); + addresses[user.address] = user.username; + } + + if (config.liquid) { + if (config.liquid.walletpass) + await lq.walletPassphrase(config.liquid.walletpass, 300); + + user.confidential = await lq.getNewAddress(); + user.liquid = (await lq.getAddressInfo(user.confidential)).unconfidential; + addresses[user.liquid] = user.username; + } + + if ( + requireChallenge && + (!challenge[ip] || + user.response.toLowerCase() !== challenge[ip].toLowerCase()) + ) { + l.info("failed challenge", ip, user.response, challenge[ip]); + throw new Error("Failed challenge"); + } + + delete challenge[ip]; + + let countries = { + CA: "CAD", + US: "USD", + JP: "JPY", + CN: "CNY", + AU: "AUD", + GB: "GBP", + }; + + if (!config.ipstack || 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 = [...new Set([user.currency, "CAD", "USD", "JPY"])]; + user.fiat = false; + user.otpsecret = authenticator.generateSecret(); + + user = await db.User.create(user); + + let account = await db.Account.create({ + user_id: user.id, + asset: config.liquid.btcasset, + balance: 0, + pending: 0, + name: "Bitcoin", + ticker: "BTC", + precision: 8, + }); + + user.accounts = [account]; + + const d = ip.split("."); + const numericIp = ((+d[0] * 256 + +d[1]) * 256 + +d[2]) * 256 + +d[3]; + if (Number.isInteger(numericIp)) { + user.ip = numericIp; + const ipExists = await db.User.findOne({ where: { ip: numericIp } }); + if (!ipExists) await gift(user); + } + + user.account_id = account.id; + await user.save(); + l.info("new user", user.username, ip); + return user; +}; diff --git a/routes/lnurl.js b/routes/lnurl.js index fad15ffe..05308d41 100644 --- a/routes/lnurl.js +++ b/routes/lnurl.js @@ -27,7 +27,7 @@ app.get("/withdraw", auth, async (req, res) => { res.send(result); } catch (e) { - l.error(e.message); + l.error("problem generating withdrawl url", e.message); res.status(500).send(e.message); } }); @@ -71,7 +71,7 @@ app.get("/login", optionalAuth, async (req, res) => { res.send(result); } catch (e) { - l.error(e.message); + l.error("problem generating login url", e.message); res.status(500).send(e.message); } }); @@ -84,7 +84,7 @@ app.get("/decode", async (req, res) => { let result = await axios.get(decoded); res.send(result.data); } catch (e) { - l.error(e.message); + l.error("problem decoding lnurl", e.message); res.status(500).send(e.message); } }); @@ -96,20 +96,29 @@ lnurlServer.bindToHook( const { key, tag, pr, k1 } = req.query; if (tag === "login") { - const username = logins[k1]; + let username = logins[k1]; + if (!username) { - user = await db.Key.findOne({ + user = (await db.Key.findOne({ where: { hex: key }, - }); + include: [{ + model: db.User, + as: 'user' + }], + })).user; if (!user) { - next(new Error("User not found")); + let ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress; + user = await register({ + username: key.substr(0, 8), + password: key, + }, ip); } + + ({ username } = user); } logins[key] = { k1, username }; - - return next(); } if (pr) { @@ -197,6 +206,7 @@ lnurlServer.bindToHook( ); lnurlServer.bindToHook("login", async (key) => { + l.info("logging in"); try { const exists = await db.Key.findOne({ where: { hex: key }, @@ -204,21 +214,28 @@ lnurlServer.bindToHook("login", async (key) => { }); let user; - if (logins[key]) { + if (logins[key] && logins[key] !== "undefined") { const { username } = logins[key]; user = await db.User.findOne({ where: { username }, }); - const k = await db.Key.create({ - user_id: user.id, - hex: key, - }); + if (user) { + const k = await db.Key.findOrCreate({ + user_id: user.id, + hex: key, + }); - l.info("added key", username, k); - emit(username, "key", k); + l.info("added key", username, k); + emit(username, "key", k); + } else { + l.info("user not found"); + user = await register({ + username: key, + password: key, + }); + } } else if (exists) ({ user } = exists); - else return; if (user && user.username) { const payload = { username: user.username }; @@ -228,6 +245,6 @@ lnurlServer.bindToHook("login", async (key) => { ws.send(JSON.stringify({ type: "token", data: token })); } } catch (e) { - l.error(e.message); + l.error("problem with login hook", e.message); } }); diff --git a/routes/users.js b/routes/users.js index 02e76b0b..a8b92860 100644 --- a/routes/users.js +++ b/routes/users.js @@ -6,16 +6,12 @@ const textToImage = require("text-to-image"); const randomWord = require("random-words"); 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 challenge = {}; - const twofa = (req, res, next) => { let { user, - body: { token } + body: { token }, } = req; if ( user.twofa && @@ -26,36 +22,11 @@ const twofa = (req, res, next) => { } else next(); }; -const gift = async user => { - const account = user.accounts[0]; - - if (faucet > 0) { - faucet -= 100; - - account.balance = 100; - await account.save(); - - const payment = await db.Payment.create({ - account_id: account.id, - user_id: user.id, - hash: "Welcome Gift", - amount: 100, - currency: user.currency, - rate: app.get("rates")[user.currency], - received: true, - confirmed: 1, - network: "GIFT" - }); - - await user.save(); - } -}; - app.get("/users/:username", async (req, res) => { const { username } = req.params; const user = await db.User.findOne({ attributes: ["username"], - where: { username } + where: { username }, }); if (user) res.send(user); else res.status(500).send("User not found"); @@ -70,94 +41,13 @@ app.get("/challenge", async (req, res) => { }); app.post("/register", async (req, res) => { - let err = m => res.status(500).send(m); - let { user } = req.body; - - if (!(user && user.username)) return err("Username required"); - - let exists = await db.User.count({ where: { username: user.username } }); - if (exists) return err("Username taken"); - - if (user.password && user.password === user.confirm) { - user.password = await bcrypt.hash(user.password, 1); - } - - if (config.bitcoin) { - if (config.bitcoin.walletpass) - await bc.walletPassphrase(config.bitcoin.walletpass, 300); - - user.address = await bc.getNewAddress("", "bech32"); - addresses[user.address] = user.username; - } - - if (config.liquid) { - if (config.liquid.walletpass) - await lq.walletPassphrase(config.liquid.walletpass, 300); - - user.confidential = await lq.getNewAddress(); - user.liquid = (await lq.getAddressInfo(user.confidential)).unconfidential; - addresses[user.liquid] = user.username; - } - - let ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress; - - if ( - !challenge[ip] || - user.response.toLowerCase() !== challenge[ip].toLowerCase() - ) { - l.info("failed challenge", ip, user.response, challenge[ip]); - return res.status(500).send("Failed challenge"); - } - - delete challenge[ip]; - - let countries = { - CA: "CAD", - US: "USD", - JP: "JPY", - CN: "CNY", - AU: "AUD", - GB: "GBP" - }; - - if (!config.ipstack || 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 = [...new Set([user.currency, "CAD", "USD", "JPY"])]; - user.otpsecret = authenticator.generateSecret(); - - user = await db.User.create(user); - - let account = await db.Account.create({ - user_id: user.id, - asset: config.liquid.btcasset, - balance: 0, - pending: 0, - name: "Bitcoin", - ticker: "BTC", - precision: 8 - }); - - user.accounts = [account]; - - const d = ip.split("."); - const numericIp = ((+d[0] * 256 + +d[1]) * 256 + +d[2]) * 256 + +d[3]; - if (Number.isInteger(numericIp)) { - user.ip = numericIp; - const ipExists = await db.User.findOne({ where: { ip: numericIp } }); - if (!ipExists) await gift(user); + 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); } - - user.account_id = account.id; - await user.save(); - res.send(pick(user, ...whitelist)); - l.info("new user", user.username, ip); }); app.post("/disable2fa", auth, twofa, async (req, res) => { @@ -209,11 +99,11 @@ app.post("/user", auth, async (req, res) => { password, passconfirm, tokens, - seed + seed, } = req.body; let exists = await db.User.findOne({ - where: { username } + where: { username }, }); let token; @@ -229,7 +119,7 @@ app.post("/user", auth, async (req, res) => { token = jwt.sign({ username }, config.jwt); res.cookie("token", token, { - expires: new Date(Date.now() + 432000000) + expires: new Date(Date.now() + 432000000), }); } @@ -259,25 +149,25 @@ app.post("/keys", auth, async (req, res) => { const { key: hex } = req.body; const key = await db.Key.create({ user_id: req.user.id, - hex + hex, }); emit(req.user.username, "key", key); }); app.post("/keys/delete", auth, async (req, res) => { const { hex } = req.body; - (await db.Key.findOne({ where: { - user_id: req.user.id, - hex - }})).destroy(); + ( + await db.Key.findOne({ + where: { + user_id: req.user.id, + hex, + }, + }) + ).destroy(); }); app.post("/login", async (req, res) => { - const { - params, - sig, - key - } = req.body; + const { params, sig, key } = req.body; if (sig) { const { callback } = params; @@ -287,7 +177,7 @@ app.post("/login", async (req, res) => { const response = await axios.get(url); res.send(response.data); } catch (e) { - l.error(e.message); + l.error("problem calling lnurl login", e.message); res.status(500).send(e.message); } @@ -339,15 +229,20 @@ app.post("/login", async (req, res) => { app.post("/logout", auth, async (req, res) => { let { subscription } = req.body; - l.info("logging out", req.user.username); + 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 + (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] + ); + res.end(); }); @@ -373,7 +268,7 @@ app.post("/account", auth, async (req, res) => { try { const account = await db.Account.findOne({ - where: { user_id, asset } + where: { user_id, asset }, }); account.name = name; @@ -396,21 +291,21 @@ app.post("/shiftAccount", auth, async (req, res) => { try { const account = await db.Account.findOne({ - where: { user_id: user.id, asset } + 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 + account_id: user.account_id, }, order: [["id", "DESC"]], limit: 12, include: { model: db.Account, - as: "account" - } + as: "account", + }, }); user.payments = payments; user.account = account; @@ -423,18 +318,18 @@ app.post("/shiftAccount", auth, async (req, res) => { } }); -setInterval(() => (faucet = 2000), DAY); - -app.get("/vapidPublicKey", function(req, res) { +app.get("/vapidPublicKey", function (req, res) { res.send(config.vapid.publicKey); }); -app.post("/subscribe", auth, async function(req, res) { +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.find( + (s) => JSON.stringify(s) === JSON.stringify(subscription) + ) ) subscriptions.push(subscription); req.user.subscriptions = subscriptions; |
