Added Auth Router and DAO

This commit is contained in:
Dharmaraj 2021-06-06 22:02:37 +05:30
parent 3df8610735
commit 4c560a437f
12 changed files with 229 additions and 133 deletions

9
.gitignore vendored
View file

@ -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
purgeCfCache.sh
backend/credentials
backend/.env

View file

@ -1,6 +1,11 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="55" name="JavaScript" />
</Languages>
</inspection_tool>
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

View file

@ -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;

View file

@ -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;

26
backend/dao/usersDAO.js Normal file
View file

@ -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;

7
backend/handlers/auth.js Normal file
View file

@ -0,0 +1,7 @@
const admin = require("firebase-admin");
module.exports = {
async verifyIdToken(idToken) {
return await admin.auth().verifyIdToken(idToken);
},
};

View file

@ -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);
},
};

22
backend/init/mongodb.js Normal file
View file

@ -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);
},
};

View file

@ -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);
}
},
};

View file

@ -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;
});
});

53
package-lock.json generated
View file

@ -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",

View file

@ -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"
}
}