From 4c560a437f0e9e647e629f0378b6e785b7c241b7 Mon Sep 17 00:00:00 2001 From: Dharmaraj <63334359+DharmarajX24@users.noreply.github.com> Date: Sun, 6 Jun 2021 22:02:37 +0530 Subject: [PATCH] Added Auth Router and DAO --- .gitignore | 9 +- .idea/inspectionProfiles/Project_Default.xml | 5 + backend/api/controllers/auth.js | 37 +++++ backend/api/routes/auth.js | 14 ++ backend/dao/usersDAO.js | 26 +++ backend/handlers/auth.js | 7 + backend/handlers/validation.js | 10 ++ backend/init/mongodb.js | 22 +++ backend/middlewares/auth.js | 16 ++ backend/server.js | 160 ++++--------------- package-lock.json | 53 +++++- package.json | 3 + 12 files changed, 229 insertions(+), 133 deletions(-) create mode 100644 backend/api/controllers/auth.js create mode 100644 backend/api/routes/auth.js create mode 100644 backend/dao/usersDAO.js create mode 100644 backend/handlers/auth.js create mode 100644 backend/handlers/validation.js create mode 100644 backend/init/mongodb.js create mode 100644 backend/middlewares/auth.js diff --git a/.gitignore b/.gitignore index abe9c5d4b..74809f3f7 100644 --- a/.gitignore +++ b/.gitignore @@ -71,6 +71,8 @@ node_modules/ .vscode *.code-workspace +.idea + #firebase functions/serviceAccountKey.json functions/serviceAccountKey_live.json @@ -78,8 +80,6 @@ functions/serviceAccountKey_copy.json functions/serviceAccountKey_live_copy.json .firebaserc .firebaserc_copy -functions/serviceAccountKey_copy.json -functions/serviceAccountKey_live_copy.json #generated files dist/ @@ -87,4 +87,7 @@ dist/ #cloudflare y .cloudflareKey.txt .cloudflareKey_copy.txt -purgeCfCache.sh \ No newline at end of file +purgeCfCache.sh + +backend/credentials +backend/.env diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 03d9549ea..fcb5fc523 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -1,6 +1,11 @@ \ No newline at end of file diff --git a/backend/api/controllers/auth.js b/backend/api/controllers/auth.js new file mode 100644 index 000000000..12f99f168 --- /dev/null +++ b/backend/api/controllers/auth.js @@ -0,0 +1,37 @@ +import UsersDAO from "../../dao/usersDAO"; +import { isUsernameValid } from "../../handlers/validation"; + +class AuthController { + static async createNewUser(req, res, next) { + try { + const { name, email, uid } = req.body; + await UsersDAO.addUser(name, email, uid); + return res.sendStatus(200); + } catch (e) { + return next(e); + } + } + + static async updateName(req, res, next) { + try { + const { name } = req.body; + if (!isUsernameValid(name)) return next("Username unavailable!"); + await UsersDAO.updateName(); + return res.sendStatus(200); + } catch (e) { + return next(e); + } + } + + static async getUser(req, res, next) { + try { + const { uid } = req.decodedToken; + const userInfo = await UsersDAO.getUser(uid); + return res.status(200).json(userInfo); + } catch (e) { + return next(e); + } + } +} + +module.exports = AuthController; diff --git a/backend/api/routes/auth.js b/backend/api/routes/auth.js new file mode 100644 index 000000000..0bed18898 --- /dev/null +++ b/backend/api/routes/auth.js @@ -0,0 +1,14 @@ +import { authenticateRequest } from "../../middlewares/auth"; + +const { Router } = require("express"); +import AuthCtrl from "../controllers/auth"; + +const router = Router(); + +router.post("/signup", AuthCtrl.createNewUser); + +router.post("/update/name", authenticateRequest, AuthCtrl.updateName); + +router.get("/user", authenticateRequest, AuthCtrl.getUser); + +module.exports = router; diff --git a/backend/dao/usersDAO.js b/backend/dao/usersDAO.js new file mode 100644 index 000000000..75041af77 --- /dev/null +++ b/backend/dao/usersDAO.js @@ -0,0 +1,26 @@ +const { mongoDB } = require("../init/mongodb"); +class UsersDAO { + static async addUser(name, email, uid) { + return await mongoDB() + .collection("users") + .insertOne({ name, email, uid, addedAt: Date.now() }); + } + + static async updateName(uid, name) { + const nameDoc = await mongoDB() + .collection("users") + .findOne({ name: { $regex: new RegExp(`^${name}$`, "i") } }); + if (nameDoc) throw new Error("Username already taken"); + return await mongoDB() + .collection("users") + .updateOne({ uid }, { $set: { name } }); + } + + static async getUser(uid) { + const user = await mongoDB().collection("users").findOne({ uid }); + if (!user) throw new Error("User not found"); + return user; + } +} + +module.exports = UsersDAO; diff --git a/backend/handlers/auth.js b/backend/handlers/auth.js new file mode 100644 index 000000000..cb91d0b64 --- /dev/null +++ b/backend/handlers/auth.js @@ -0,0 +1,7 @@ +const admin = require("firebase-admin"); + +module.exports = { + async verifyIdToken(idToken) { + return await admin.auth().verifyIdToken(idToken); + }, +}; diff --git a/backend/handlers/validation.js b/backend/handlers/validation.js new file mode 100644 index 000000000..ff1a494cc --- /dev/null +++ b/backend/handlers/validation.js @@ -0,0 +1,10 @@ +module.exports = { + isUsernameValid(name) { + if (name === null || name === undefined || name === "") return false; + if (/miodec/.test(name.toLowerCase())) return false; + if (/bitly/.test(name.toLowerCase())) return false; + if (name.length > 14) return false; + if (/^\..*/.test(name.toLowerCase())) return false; + return /^[0-9a-zA-Z_.-]+$/.test(name); + }, +}; diff --git a/backend/init/mongodb.js b/backend/init/mongodb.js new file mode 100644 index 000000000..b3ab928ae --- /dev/null +++ b/backend/init/mongodb.js @@ -0,0 +1,22 @@ +const { MongoClient } = require("mongodb"); + +let mongoClient; + +module.exports = { + async connectDB() { + return MongoClient.connect(process.env.DB_URI, { + useNewUrlParser: true, + useUnifiedTopology: true, + }) + .then((client) => { + mongoClient = client; + }) + .catch((e) => { + console.log(e); + process.exit(1); + }); + }, + mongoDB() { + return mongoClient.db(process.env.DB_NAME); + }, +}; diff --git a/backend/middlewares/auth.js b/backend/middlewares/auth.js new file mode 100644 index 000000000..a3ab192ad --- /dev/null +++ b/backend/middlewares/auth.js @@ -0,0 +1,16 @@ +const { verifyIdToken } = require("../handlers/auth"); + +module.exports = { + async authenticateRequest(req, res, next) { + try { + const { authorization } = req.headers; + if (!authorization) return next("Unauthorized"); + const token = authorization.split(" "); + if (token[0] !== "Bearer ") return next("Invalid token"); + req.decodedToken = await verifyIdToken(token[1]); + return next(); + } catch (e) { + return next(e); + } + }, +}; diff --git a/backend/server.js b/backend/server.js index 478225b98..514734447 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,6 +1,10 @@ const express = require("express"); +const { config } = require("dotenv"); +const path = require("path"); +config({ path: path.join(__dirname, ".env") }); const bodyParser = require("body-parser"); const mongoose = require("mongoose"); +const { MongoClient } = require("mongodb"); const cors = require("cors"); const admin = require("firebase-admin"); const helmet = require("helmet"); @@ -11,7 +15,8 @@ const { Stats } = require("./models/stats"); // Firebase admin setup //currently uses account key in functions to prevent repetition -const serviceAccount = require("../functions/serviceAccountKey.json"); +const serviceAccount = require("./credentials/serviceAccountKey.json"); +const { connectDB } = require("./init/mongodb"); admin.initializeApp({ credential: admin.credential.cert(serviceAccount), @@ -19,19 +24,24 @@ admin.initializeApp({ // MIDDLEWARE & SETUP const app = express(); +app.use(express.urlencoded({ extended: true })); +app.use(express.json()); app.use(cors()); app.use(helmet()); const port = process.env.PORT || "5005"; -mongoose.connect("mongodb://localhost:27017/monkeytype", { - useNewUrlParser: true, - useUnifiedTopology: true, -}); - -const mtRootDir = __dirname.substring(0, __dirname.length - 8); //will this work for windows and mac computers? -app.use(express.static(mtRootDir + "/dist")); -app.use(bodyParser.json()); +connectDB() + .then(() => { + app.listen(process.env.PORT, () => { + console.log(`listening on port ${process.env.PORT}`); + }); + }) + .catch((e) => { + console.log(e); + }); +const authRouter = require("./api/routes/auth"); +app.use("/auth", authRouter); // Daily leaderboard clear function function clearDailyLeaderboards() { @@ -351,10 +361,10 @@ async function checkIfTagPB(obj, userdata) { if (restags.includes(doc._id.toString())) { //not sure what this is supposed to do /* - let data = doc.data(); - data.id = doc.id; - dbtags.push(data); - */ + let data = doc.data(); + data.id = doc.id; + dbtags.push(data); + */ dbtags.push(doc); } }); @@ -518,7 +528,6 @@ function incrementT60Bananas(uid, result, userData) { if (best60 != undefined && result.wpm < best60 - best60 * 0.25) { // console.log("returning"); - return; } else { //increment // console.log("checking"); @@ -608,88 +617,8 @@ function isTagPresetNameValid(name) { return /^[0-9a-zA-Z_.-]+$/.test(name); } -function isUsernameValid(name) { - if (name === null || name === undefined || name === "") return false; - if (/miodec/.test(name.toLowerCase())) return false; - if (/bitly/.test(name.toLowerCase())) return false; - if (name.length > 14) return false; - if (/^\..*/.test(name.toLowerCase())) return false; - return /^[0-9a-zA-Z_.-]+$/.test(name); -} - // API -app.get("/nameCheck/:name", (req, res) => { - if (!isUsernameValid(req.params.name)) { - res.status(200).send({ - resultCode: -2, - message: "Username is not valid", - }); - return; - } - User.findOne({ name: req.params.name }, (err, user) => { - console.log(err); - if (user) { - res.status(200).send({ - resultCode: -1, - message: "Username is taken", - }); - return; - } else { - res.status(200).send({ - resultCode: 1, - message: "Username is available", - }); - return; - } - }).catch(() => { - res.status(200).send({ - resultCode: -1, - message: "Error when checking for names", - }); - }); -}); - -app.post("/signUp", (req, res) => { - const newuser = new User({ - name: req.body.name, - email: req.body.email, - uid: req.body.uid, - }); - newuser.save(); - res.status(200); - res.json({ user: newuser }); - return; -}); - -app.post("/updateName", authenticateToken, (req, res) => { - if (isUsernameValid(name)) { - User.findOne({ uid: req.uid }, (err, user) => { - User.findOne({ name: req.body.name }, (err2, user2) => { - if (!user2) { - user.name = req.body.name; - user.save(); - res.status(200).send({ status: 1 }); - } else { - res.status(200).send({ status: -1, message: "Username taken" }); - } - }); - }); - } else { - res.status(200).send({ status: -1, message: "Username invalid" }); - } -}); - -app.get("/fetchSnapshot", authenticateToken, (req, res) => { - User.findOne({ uid: req.uid }, (err, user) => { - if (err) res.status(500).send({ error: err }); - if (!user) res.status(200).send({ message: "No user found" }); //client doesn't do anything with this - let snap = user; - res.send({ snap: snap }); - return; - }); -}); - function stdDev(array) { const n = array.length; const mean = array.reduce((a, b) => a + b) / n; @@ -740,6 +669,7 @@ app.post("/testCompleted", authenticateToken, (req, res) => { } return errCount; } + let errCount = verifyValue(obj); if (errCount > 0) { console.error( @@ -960,13 +890,13 @@ app.post("/testCompleted", authenticateToken, (req, res) => { `saved result for ${req.uid} (new PB) - ${JSON.stringify(logobj)}` ); /* - User.findOne({ name: userdata.name }, (err, user2) => { - console.log(user2.results[user2.results.length-1]) - console.log(user2.results[user2.results.length-1]).isPb - user2.results[user2.results.length-1].isPb = true; - user2.save(); - }) - */ + User.findOne({ name: userdata.name }, (err, user2) => { + console.log(user2.results[user2.results.length-1]) + console.log(user2.results[user2.results.length-1]).isPb + user2.results[user2.results.length-1].isPb = true; + user2.save(); + }) + */ request.obj.isPb = true; if ( obj.mode === "time" && @@ -994,7 +924,6 @@ app.post("/testCompleted", authenticateToken, (req, res) => { } stripAndSave(req.uid, request.obj); res.status(200).send(returnobj); - return; }) .catch((e) => { console.error( @@ -1003,7 +932,6 @@ app.post("/testCompleted", authenticateToken, (req, res) => { res .status(200) .send({ data: { resultCode: -999, message: e.message } }); - return; }); } catch (e) { console.error( @@ -1012,7 +940,6 @@ app.post("/testCompleted", authenticateToken, (req, res) => { )} - ${e}` ); res.status(200).send({ resultCode: -999, message: e.message }); - return; } }); }); @@ -1041,7 +968,6 @@ app.post("/clearTagPb", authenticateToken, (req, res) => { resultCode: -999, message: e.message, }); - return; }); res.sendStatus(200); }); @@ -1062,21 +988,18 @@ app.post("/unlinkDiscord", authenticateToken, (req, res) => { status: 1, message: "Unlinked", }); - return; }) .catch((e) => { res.status(200).send({ status: -999, message: e.message, }); - return; }); } catch (e) { res.status(200).send({ status: -999, message: e, }); - return; } }); @@ -1538,7 +1461,6 @@ app.post("/verifyDiscord", authenticateToken, (req, res) => { message: "This Discord account is already paired to a different Monkeytype account", }); - return; } else { User.findOne({ uid: req.uid }, (err, user2) => { user2.discordId = did; @@ -1553,7 +1475,6 @@ app.post("/verifyDiscord", authenticateToken, (req, res) => { res .status(200) .send({ status: 1, message: "Verified", did: did }); - return; }); } }); @@ -1564,11 +1485,9 @@ app.post("/verifyDiscord", authenticateToken, (req, res) => { e.message ); response.status(200).send({ status: -1, message: e.message }); - return; }); } catch (e) { response.status(200).send({ status: -1, message: e }); - return; } }); @@ -1678,10 +1597,10 @@ app.post("/attemptAddToLeaderboards", authenticateToken, (req, res) => { return; } /* - if (user.verified === false) { - res.status(200).send({ needsToVerify: true }); - return; - }*/ + if (user.verified === false) { + res.status(200).send({ needsToVerify: true }); + return; + }*/ Leaderboard.find( { mode: result.mode, @@ -1781,7 +1700,6 @@ app.get("/getUserDiscordData/:uid", botAuth, (req, res) => { //for announceDailyLbResult User.findOne({ uid: req.body.uid }, (err, user) => { res.send({ name: user.name, discordId: user.discordId }); - return; }); }); @@ -1790,10 +1708,8 @@ app.get("/getUserPbs/:discordId", botAuth, (req, res) => { User.findOne({ discordId: req.params.discordId }, (err, user) => { if (user) { res.send({ personalBests: user.personalBests }); - return; } else { res.send({ error: "No user found with that id" }); - return; } }); }); @@ -1803,10 +1719,8 @@ app.get("/getUserPbsByUid/:uid", botAuth, (req, res) => { User.findOne({ uid: req.params.uid }, (err, user) => { if (user) { res.send({ personalBests: user.personalBests }); - return; } else { res.send({ error: "No user found with that id" }); - return; } }); }); @@ -1821,7 +1735,6 @@ app.get("/getTimeLeaderboard/:mode2/:type", botAuth, (req, res) => { //get top 10 leaderboard lb.board.length = 10; res.send({ board: lb.board }); - return; }); }); @@ -1833,7 +1746,6 @@ app.get("/getUserByDiscordId/:discordId", botAuth, (req, res) => { } else { res.send({ error: "No user found with that id" }); } - return; }); }); @@ -1848,7 +1760,6 @@ app.get("/getRecentScore/:discordId", botAuth, (req, res) => { } else { res.send({ error: "No user found with that id" }); } - return; }); }); @@ -1860,7 +1771,6 @@ app.get("/getUserStats/:discordId", botAuth, (req, res) => { } else { res.send({ error: "No user found with that id" }); } - return; }); }); diff --git a/package-lock.json b/package-lock.json index 956caae78..8d5f5e226 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3607,6 +3607,11 @@ "is-obj": "^2.0.0" } }, + "dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==" + }, "duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -7504,9 +7509,9 @@ "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" }, "mongodb": { - "version": "3.6.8", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.8.tgz", - "integrity": "sha512-sDjJvI73WjON1vapcbyBD3Ao9/VN3TKYY8/QX9EPbs22KaCSrQ5rXo5ZZd44tWJ3wl3FlnrFZ+KyUtNH6+1ZPQ==", + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.9.tgz", + "integrity": "sha512-1nSCKgSunzn/CXwgOWgbPHUWOO5OfERcuOWISmqd610jn0s8BU9K4879iJVabqgpPPbA6hO7rG48eq+fGED3Mg==", "requires": { "bl": "^2.2.1", "bson": "^1.1.4", @@ -7533,6 +7538,21 @@ "safe-buffer": "5.2.1", "sift": "13.5.2", "sliced": "1.0.1" + }, + "dependencies": { + "mongodb": { + "version": "3.6.8", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.8.tgz", + "integrity": "sha512-sDjJvI73WjON1vapcbyBD3Ao9/VN3TKYY8/QX9EPbs22KaCSrQ5rXo5ZZd44tWJ3wl3FlnrFZ+KyUtNH6+1ZPQ==", + "requires": { + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "optional-require": "^1.0.3", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + } + } } }, "mongoose-legacy-pluralize": { @@ -8316,6 +8336,30 @@ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", "dev": true }, + "path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", + "requires": { + "process": "^0.11.1", + "util": "^0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "requires": { + "inherits": "2.0.3" + } + } + } + }, "path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", @@ -8593,8 +8637,7 @@ "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" }, "process-nextick-args": { "version": "2.0.1", diff --git a/package.json b/package.json index 5842a2288..f234ca5cc 100644 --- a/package.json +++ b/package.json @@ -48,12 +48,15 @@ "chartjs-plugin-annotation": "^0.5.7", "chartjs-plugin-trendline": "^0.2.2", "cors": "^2.8.5", + "dotenv": "^10.0.0", "express": "^4.17.1", "firebase-admin": "^9.9.0", "helmet": "^4.6.0", "howler": "^2.2.1", + "mongodb": "^3.6.9", "mongoose": "^5.12.12", "nodemon": "^2.0.7", + "path": "^0.12.7", "tinycolor2": "^1.4.2" } }