diff options
| author | Adam Soltys <[email protected]> | 2020-02-12 10:08:26 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2020-02-12 10:08:26 -0800 |
| commit | 9fb58c7b2765646f95139c89e7339114a46a9948 (patch) | |
| tree | c2b96c0f154221365a8ea02c781d44b129d787a2 | |
| parent | 3303c4e0e08d778a80a8592bf1339acdc0ca3b0a (diff) | |
| download | coinos-server-9fb58c7b2765646f95139c89e7339114a46a9948.tar.gz coinos-server-9fb58c7b2765646f95139c89e7339114a46a9948.zip | |
2fa
| -rw-r--r-- | models/users.js | 9 | ||||
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | schema.sql | 74 | ||||
| -rw-r--r-- | sendLiquid.js | 5 | ||||
| -rw-r--r-- | users.js | 77 | ||||
| -rw-r--r-- | yarn.lock | 67 |
6 files changed, 162 insertions, 71 deletions
diff --git a/models/users.js b/models/users.js index 8edc94b0..467a0e3b 100644 --- a/models/users.js +++ b/models/users.js @@ -85,6 +85,15 @@ module.exports = sequelize => { 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, diff --git a/package.json b/package.json index b63e65bf..c08f1a2d 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "morgan": "^1.9.1", "mysql": "^2.17.1", "mysql2": "^1.6.5", + "otplib": "^12.0.1", "passport": "0.4.0", "passport-jwt": "^4.0.0", "passport-local": "1.0.0", @@ -1,8 +1,8 @@ --- MySQL dump 10.17 Distrib 10.3.12-MariaDB, for debian-linux-gnu (x86_64) +-- MySQL dump 10.17 Distrib 10.3.22-MariaDB, for debian-linux-gnu (x86_64) -- -- Host: localhost Database: coinos -- ------------------------------------------------------ --- Server version 10.3.12-MariaDB-1:10.3.12+maria~bionic +-- Server version 10.3.22-MariaDB-1:10.3.22+maria~bionic /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; @@ -31,24 +31,6 @@ CREATE TABLE `SequelizeMeta` ( /*!40101 SET character_set_client = @saved_cs_client */; -- --- Table structure for table `orders` --- - -DROP TABLE IF EXISTS `orders`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `orders` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `amount` int(11) DEFAULT NULL, - `price` int(11) DEFAULT NULL, - `type` varchar(255) DEFAULT NULL, - `pair` varchar(255) DEFAULT NULL, - `createdAt` datetime DEFAULT current_timestamp(), - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -/*!40101 SET character_set_client = @saved_cs_client */; - --- -- Table structure for table `payments` -- @@ -58,18 +40,20 @@ DROP TABLE IF EXISTS `payments`; CREATE TABLE `payments` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) DEFAULT NULL, - `hash` varchar(255) DEFAULT NULL, + `hash` text DEFAULT NULL, `createdAt` datetime NOT NULL, `updatedAt` datetime NOT NULL, `rate` double DEFAULT NULL, - `currency` varchar(255) DEFAULT NULL, - `address` int(11) DEFAULT NULL, + `currency` varchar(255) NOT NULL, + `address` varchar(255) DEFAULT NULL, `received` tinyint(1) DEFAULT NULL, `amount` double DEFAULT NULL, `tip` double DEFAULT NULL, - `confirmed` tinyint(1) DEFAULT NOT NULL, + `confirmed` tinyint(1) NOT NULL, + `fee` double DEFAULT NULL, + `asset` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=1611 DEFAULT CHARSET=latin1; +) ENGINE=InnoDB AUTO_INCREMENT=2782 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -87,7 +71,7 @@ CREATE TABLE `users` ( `address` varchar(255) DEFAULT NULL, `unit` varchar(255) DEFAULT NULL, `symbol` varchar(255) DEFAULT NULL, - `currency` varchar(255) NOT NULL DEFAULT 'CAD', + `currency` varchar(255) DEFAULT NULL, `commission` varchar(255) DEFAULT NULL, `title` varchar(255) DEFAULT NULL, `pubkey` varchar(255) DEFAULT NULL, @@ -106,42 +90,18 @@ CREATE TABLE `users` ( `pic` varchar(255) DEFAULT NULL, `emailToken` varchar(255) DEFAULT NULL, `phone` varchar(255) DEFAULT NULL, - `phoneVerified` varchar(255) DEFAULT NULL, + `phoneVerified` tinyint(1) DEFAULT NULL, `twofa` tinyint(1) DEFAULT NULL, `authyId` varchar(255) DEFAULT NULL, `pin` varchar(255) DEFAULT NULL, `emailVerified` tinyint(1) DEFAULT NULL, `phoneToken` varchar(255) DEFAULT NULL, + `confidential` varchar(255) DEFAULT NULL, + `liquid` varchar(255) DEFAULT NULL, + `currencies` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL, + `otp` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=305 DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `users_balances` --- - -DROP TABLE IF EXISTS `users_balances`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `users_balances` ( - `user_id` int(11) DEFAULT NULL, - `symbol` varchar(255) DEFAULT NULL, - `balance` int(11) DEFAULT NULL, - `pending` int(11) DEFAULT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `users_channels` --- - -DROP TABLE IF EXISTS `users_channels`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `users_channels` ( - `user_id` int(11) DEFAULT NULL, - `channel_id` int(11) DEFAULT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +) ENGINE=InnoDB AUTO_INCREMENT=420 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; @@ -153,4 +113,4 @@ CREATE TABLE `users_channels` ( /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2019-02-09 15:40:59 +-- Dump completed on 2020-02-09 15:22:43 diff --git a/sendLiquid.js b/sendLiquid.js index 1a218afe..b9261493 100644 --- a/sendLiquid.js +++ b/sendLiquid.js @@ -30,6 +30,7 @@ module.exports = (app, db, emit) => async (req, res) => { fee = parseInt(fee * SATS); total = amount + fee; } catch (e) { + l("funding failed", e); return res.status(500).send(e.message); } @@ -52,7 +53,7 @@ module.exports = (app, db, emit) => async (req, res) => { await user.save({ transaction }); }); } catch (e) { - l(e); + l("balance check failed", e); return res.status(500).send("Not enough satoshis"); } @@ -87,7 +88,7 @@ module.exports = (app, db, emit) => async (req, res) => { res.send(payment); }); } catch (e) { - l(e); + l("send failed", e); return res.status(500).send(e.message); } }; @@ -12,12 +12,19 @@ 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 = console.log; 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(); + else res.status(401).end(); +}; + module.exports = (addresses, auth, app, bc, db, emit) => { app.get("/liquidate", async (req, res) => { let users = await db.User.findAll(); @@ -30,6 +37,11 @@ module.exports = (addresses, auth, app, bc, db, emit) => { )).unconfidential; await user.save(); } + + if (!user.otpsecret) { + user.otpsecret = authenticator.generateSecret(); + await user.save(); + } } res.end(); }); @@ -40,17 +52,17 @@ module.exports = (addresses, auth, app, bc, db, emit) => { faucet -= 100; const payment = await db.Payment.create({ user_id: user.id, - hash: 'Welcome Gift', + hash: "Welcome Gift", amount: 100, currency: user.currency, rate: app.get("rates")[user.currency], received: true, confirmed: 1, - asset: 'GIFT', + asset: "GIFT" }); await user.save(); } - } + }; app.post("/register", async (req, res) => { let err = m => res.status(500).send(m); @@ -66,22 +78,27 @@ module.exports = (addresses, auth, app, bc, db, emit) => { user.confidential )).unconfidential; user.name = user.username; - let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; + let ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress; let countries = { CA: "CAD", US: "USD", JP: "JPY", CN: "CNY", AU: "AUD", - GB: "GBP", + GB: "GBP" }; - if (ip === "127.0.0.1") - user.currency = "CAD"; - else - user.currency = countries[(await axios.get(`http://api.ipstack.com/${ip}?access_key=${config.ipstack}`)).data.country_code] || "EUR"; + if (ip === "127.0.0.1") user.currency = "CAD"; + else + user.currency = + countries[ + (await axios.get( + `http://api.ipstack.com/${ip}?access_key=${config.ipstack}` + )).data.country_code + ] || "EUR"; user.currencies = [user.currency]; + user.otpsecret = authenticator.generateSecret(); addresses[user.address] = user.username; addresses[user.liquid] = user.username; @@ -141,6 +158,39 @@ module.exports = (addresses, auth, app, bc, db, emit) => { 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); + 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(e); + } + res.end(); + }); + app.post("/user", auth, async (req, res) => { try { let { user } = req; @@ -167,7 +217,9 @@ module.exports = (addresses, auth, app, bc, db, emit) => { user.username = username; addresses[user.address] = username; token = jwt.sign({ username }, config.jwt); - res.cookie("token", token, { expires: new Date(Date.now() + 432000000) }); + res.cookie("token", token, { + expires: new Date(Date.now() + 432000000) + }); } if (email && user.email !== email) { @@ -303,7 +355,8 @@ module.exports = (addresses, auth, app, bc, db, emit) => { if ( !user || - (user.password && !(await bcrypt.compare(req.body.password, user.password))) || + (user.password && + !(await bcrypt.compare(req.body.password, user.password))) || (user.twofa && !(await authyVerify(user))) ) { return res.status(401).end(); @@ -419,5 +472,5 @@ module.exports = (addresses, auth, app, bc, db, emit) => { } }); - setInterval(() => faucet = 2000, DAY) + setInterval(() => (faucet = 2000), DAY); }; @@ -161,6 +161,54 @@ __metadata: languageName: node linkType: hard +"@otplib/core@npm:^12.0.1": + version: 12.0.1 + resolution: "@otplib/core@npm:12.0.1" + checksum: 3a73c409dc4e9a3dbb8d1e4bd3cb438883c0c7f287c8a7485ef558b47e8e72af18e23e4817d085b9f5e40ce970d7b0ce11ae5524692927c964c80ce399840480 + languageName: node + linkType: hard + +"@otplib/plugin-crypto@npm:^12.0.1": + version: 12.0.1 + resolution: "@otplib/plugin-crypto@npm:12.0.1" + dependencies: + "@otplib/core": ^12.0.1 + checksum: f83458ed5a4ac91118d7bc113385d0ab296c7b97e243903a887f2097c1b03421e5f670be60f808d96a9934f3a0049aceb2af02f71e54d5f22f065000b841a0c6 + languageName: node + linkType: hard + +"@otplib/plugin-thirty-two@npm:^12.0.1": + version: 12.0.1 + resolution: "@otplib/plugin-thirty-two@npm:12.0.1" + dependencies: + "@otplib/core": ^12.0.1 + thirty-two: ^1.0.2 + checksum: 4deec8586480f880239a087f6f6599eb4425abdda184e383112f97993bb5961943fdb435fbea086b1f9d1116768ec0c2fb6df9e8a0dbb854b7c0765dbbdcd9fe + languageName: node + linkType: hard + +"@otplib/preset-default@npm:^12.0.1": + version: 12.0.1 + resolution: "@otplib/preset-default@npm:12.0.1" + dependencies: + "@otplib/core": ^12.0.1 + "@otplib/plugin-crypto": ^12.0.1 + "@otplib/plugin-thirty-two": ^12.0.1 + checksum: 32b214c0f750c793147f1e988093959aef8eded36c9d7df2c2c7ff4e4fb54de85b76b5ff28ee8ee39b1572c933d205c081a3c8f2df192199813b3572e61a7305 + languageName: node + linkType: hard + +"@otplib/preset-v11@npm:^12.0.1": + version: 12.0.1 + resolution: "@otplib/preset-v11@npm:12.0.1" + dependencies: + "@otplib/core": ^12.0.1 + "@otplib/plugin-crypto": ^12.0.1 + "@otplib/plugin-thirty-two": ^12.0.1 + checksum: 89366b1c52a8de2a1cd434c9fd534d97fcde0b103f92d15ff3cf37ed1d3579746b7b4b70efe398aa532ab44e15daa008d471f90f8b4de04f8cba81c05dfda7de + languageName: node + linkType: hard + "@pm2/agent-node@npm:^1.1.10": version: 1.1.10 resolution: "@pm2/agent-node@npm:1.1.10" @@ -1885,6 +1933,7 @@ __metadata: mysql: ^2.17.1 mysql2: ^1.6.5 nodemon: ^2.0.2 + otplib: ^12.0.1 passport: 0.4.0 passport-jwt: ^4.0.0 passport-local: 1.0.0 @@ -6070,6 +6119,17 @@ fsevents@~2.1.2: languageName: node linkType: hard +"otplib@npm:^12.0.1": + version: 12.0.1 + resolution: "otplib@npm:12.0.1" + dependencies: + "@otplib/core": ^12.0.1 + "@otplib/preset-default": ^12.0.1 + "@otplib/preset-v11": ^12.0.1 + checksum: 93b006b69d7c51fa1e8d6e7a661c1208aa9410be10274a8eb81dcd1d2b1b08c3b9f1e2ef4e10d2a4ad4ef3a8f3e43da4830d21d674161a4fa43ac9e23594c437 + languageName: node + linkType: hard + "p-cancelable@npm:^0.3.0": version: 0.3.0 resolution: "p-cancelable@npm:0.3.0" @@ -8350,6 +8410,13 @@ fsevents@~2.1.2: languageName: node linkType: hard +"thirty-two@npm:^1.0.2": + version: 1.0.2 + resolution: "thirty-two@npm:1.0.2" + checksum: 573755c8a09da50666b4354fe6b6f8624cf2bfcf2c71ffbed531b6d73b988e1907be792ef23664749144978620ddb7026a158fc1c72d3eee4545b422dfc4e814 + languageName: node + linkType: hard + "through@npm:^2.3.8": version: 2.3.8 resolution: "through@npm:2.3.8" |
