Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const projectsRouter = require("./routers/projects.router");
const recurringEventsRouter = require("./routers/recurringEvents.router");
const projectTeamMembersRouter = require("./routers/projectTeamMembers.router");
const slackRouter = require("./routers/slack.router");
const authRouter = require("./routers/auth.router");

app.use("/api/events", eventsRouter);
app.use("/api/checkins", checkInsRouter);
Expand All @@ -56,6 +57,7 @@ app.use("/api/projects", projectsRouter);
app.use("/api/recurringevents", recurringEventsRouter);
app.use("/api/projectteammembers", projectTeamMembersRouter);
app.use("/api/slack", slackRouter);
app.use("/api/auth", authRouter);
const CLIENT_BUILD_PATH = path.join(__dirname, "../client/build");

// Serve static files from the React frontend app
Expand Down
5 changes: 5 additions & 0 deletions backend/config/auth.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
SECRET:
"c0d7d0716e4cecffe9dcc77ff90476d98f5aace08ea40f5516bd982b06401021191f0f24cd6759f7d8ca41b64f68d0b3ad19417453bddfd1dbe8fcb197245079",
CUSTOM_REQUEST_HEADER: process.env.CUSTOM_REQUEST_HEADER,
};
6 changes: 6 additions & 0 deletions backend/config/auth.config.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const { expectCt } = require("helmet");

test("Environment variables are working as expected", () => {
const backendUrl = process.env.REACT_APP_PROXY;
expect(backendUrl === `http://localhost:${process.env.BACKEND_PORT}`);
});
77 changes: 77 additions & 0 deletions backend/controllers/email.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const nodemailer = require("nodemailer");
const { google } = require("googleapis");
const OAuth2 = google.auth.OAuth2;

const jwt = require("jsonwebtoken");

const CLIENT_ID = process.env.GMAIL_CLIENT_ID;
const SECRET_ID = process.env.GMAIL_SECRET_ID;
const REFRESH_TOKEN = process.env.GMAIL_REFRESH_TOKEN;
const EMAIL_ACCOUNT = process.env.GMAIL_EMAIL;

async function mailServer(email, token) {
const oauth2Client = new OAuth2(
CLIENT_ID, // ClientID
SECRET_ID, // Client Secret
"https://developers.google.com/oauthplayground" // Redirect URL
);

oauth2Client.setCredentials({
refresh_token: REFRESH_TOKEN,
});
const accessToken = oauth2Client.getAccessToken();

let smtpTransport;
if (process.env.NODE_ENV === "test") {
// Send mail to Mailhog Docker container
smtpTransport = nodemailer.createTransport({
host: "127.0.0.1",
port: 1025,
auth: {
user: "user",
pass: "password",
},
});
} else {
smtpTransport = nodemailer.createTransport({
service: "gmail",
auth: {
type: "OAuth2",
user: EMAIL_ACCOUNT,
clientId: CLIENT_ID,
clientSecret: SECRET_ID,
refreshToken: REFRESH_TOKEN,
accessToken: accessToken,
},
});
}
const appUrl = process.env.REACT_APP_PROXY;
const encodedToken = encodeURIComponent(token);
const emailLink = `https://tinyurl.com/2drxdk/auth/me?token=${encodedToken}`;
const encodedUri = encodeURI(emailLink);
const mailOptions = {
from: EMAIL_ACCOUNT,
to: email,
subject: "VRMS Magic link 🎩 !",
html: `<a href=${encodedUri}>
LOGIN HERE
</a>`,
text: `Magic link: ${encodedUri}`,
};

if (process.env.NODE_ENV === "test") {
smtpTransport.sendMail(mailOptions, (error, response) => {
console.log("email sent");
smtpTransport.close();
});
} else {
smtpTransport.sendMail(mailOptions, (error, response) => {
error ? console.log(error) : console.log(response);
smtpTransport.close();
});
}
}

exports.sendUserEmailSigninLink = async (email, token) => {
await mailServer(email, token);
};
104 changes: 104 additions & 0 deletions backend/controllers/user.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
const CONFIG = require("../config/auth.config");
const DB = require("../models");
const emailController = require("./email.controller");
const User = DB.user;

var jwt = require("jsonwebtoken");

const { body, validationResult } = require("express-validator");

function generateAccessToken(user) {
// expires after half and hour (1800 seconds = 30 minutes)
return jwt.sign({ id: user.id }, CONFIG.SECRET, { expiresIn: "1800s" });
}

function createUser(req, res) {
const user = new User({
name: {
firstName: req.body.firstName,
lastName: req.body.lastName,
},
email: req.body.email,
accessLevel: "user",
});

user.save((err, user) => {
if (err) {
res.status(500).send({ message: err });
return;
} else {
return res
.status(200)
.send({ message: "User was registered successfully!" });
}
});

const jsonToken = generateAccessToken(user);
emailController.sendUserEmailSigninLink(req.body.email, jsonToken);
}

function signin(req, res) {
const { email } = req.body;
console.log(email);

User.findOne({ email })
.then((user) => {
if (!user) {
res.status(401).send({ message: "User not authorized" });
} else {
const jsonToken = generateAccessToken(user);
emailController.sendUserEmailSigninLink(req.body.email, jsonToken);
return res
.status(200)
.send({ message: "User login link sent to email!" });
}
})
.catch((err) => {
console.log(err);

res.status(400).send({ message: "User email not found." });
});
}

async function validateCreateUserAPICall(req, res, next) {
await body("name.firstName").not().isEmpty().trim().escape().run(req);
await body("name.lastName").not().isEmpty().trim().escape().run(req);
await body("email", "Invalid email")
.exists()
.isEmail()
.normalizeEmail()
.run(req);

// Finds the validation errors in this request and wraps them in an object with handy functions
const errors = validationResult(req);

if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
next();
}

async function validateSigninUserAPICall(req, res, next) {
await body("email", "Invalid email")
.exists()
.isEmail()
.normalizeEmail()
.run(req);

// Finds the validation errors in this request and wraps them in an object with handy functions
const errors = validationResult(req);

if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
next();
}

userController = {
validateCreateUserAPICall,
validateSigninUserAPICall,
createUser,
signin,
};

module.exports = userController;
23 changes: 23 additions & 0 deletions backend/middleware/authJwt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const jwt = require("jsonwebtoken");
const CONFIG = require("../config/auth.config.js");

function verifyToken(req, res, next) {
let token = req.headers["x-access-token"];

if (!token) {
return res.status(403).send({ message: "No token provided!" });
}

jwt.verify(token, CONFIG.SECRET, (err, decoded) => {
if (err) {
return res.status(401).send({ message: "Unauthorized!" });
}
req.userId = decoded.id;
next();
});
}

const authJwt = {
verifyToken,
};
module.exports = authJwt;
7 changes: 7 additions & 0 deletions backend/middleware/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const authJwt = require("./authJwt");
const verifyUser = require("./verifyUser");

module.exports = {
authJwt,
verifyUser,
};
43 changes: 43 additions & 0 deletions backend/middleware/verifyUser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const db = require("../models");
const User = db.user;

function checkDuplicateEmail(req, res, next) {
User.findOne({ email: req.body.email }).then((user) => {
if (user) {
res.status(400).send({
message: "Failed! Email is already in use!",
});

return;
}
next();
});
}

function isAdmin(req, res, next) {
User.findOne({ email: req.body.email }).then((user) => {
if (!user) {
res.status(400).send({
message: "User does not exist",
});
} else {
const role = user.accessLevel;
if (role === "admin") {
next();
} else {
next(
res.status(401).send({
message: "Invalid permissions",
})
);
}
}
});
}

const verifyUser = {
checkDuplicateEmail,
isAdmin,
};

module.exports = verifyUser;
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
const Answer = require("../../models/answer.model");
const dbHandler = require("../db-handler");
const Answer = require("./answer.model");

// Required database setup and teardown
beforeAll(async () => await dbHandler.connect());
afterAll(async () => await dbHandler.closeDatabase());
const { setupDB } = require("../setup-test");
setupDB("answer-model");

describe("Answer Model saves the correct values", () => {
const submittedAnswer = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
const Checkin = require("../../models/answer.model");
const dbHandler = require("../db-handler");
const Checkin = require("./checkIn.model");

// Required database setup and teardown
beforeAll(async () => await dbHandler.connect());
afterAll(async () => await dbHandler.closeDatabase());
const { setupDB } = require("../setup-test");
setupDB("checkin-model");

describe("Checkin Model saves the correct values", () => {
test("Save a model instance and then read from the db", async (done) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
const Event = require("../../models/event.model");
const dbHandler = require("../db-handler");
const Event = require("./event.model");

// Required database setup and teardown
beforeAll(async () => await dbHandler.connect());
afterAll(async () => await dbHandler.closeDatabase());
const { setupDB } = require("../setup-test");
setupDB("event-model");

describe("Event Model saves the correct values", () => {
test("Save a model instance and then read from the db", async (done) => {
Expand Down
18 changes: 18 additions & 0 deletions backend/models/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const mongoose = require("mongoose");
mongoose.Promise = global.Promise;

const db = {};

db.mongoose = mongoose;

db.answer = require("./answer.model");
db.checkin = require("./checkIn.model");
db.event = require("./event.model");
db.project = require("./project.model");
db.projectTeamMember = require("./projectTeamMember.model");
db.question = require("./question.model");
db.recurringEvent = require("./recurringEvent.model");
db.role = require("./role.model");
db.user = require("./user.model");

module.exports = db;
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
const Project = require("../../models/project.model");
const dbHandler = require("../db-handler");
const Project = require("./project.model");

// Required database setup and teardown
beforeAll(async () => await dbHandler.connect());
afterAll(async () => await dbHandler.closeDatabase());
const { setupDB } = require("../setup-test");
setupDB("project-model");

describe("Project Model saves the correct values", () => {
test("Save a model instance and then read from the db", async (done) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
const ProjectTeamMember = require("../../models/project.model");
const dbHandler = require("../db-handler");
const ProjectTeamMember = require("./projectTeamMember.model");

// Required database setup and teardown
beforeAll(async () => await dbHandler.connect());
afterAll(async () => await dbHandler.closeDatabase());
const { setupDB } = require("../setup-test");
setupDB("projectTeamMember-model");

// Please add and expand on this simple test.
describe("ProjectTeamMember Model saves the correct values", () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
const Question = require("../../models/question.model");
const dbHandler = require("../db-handler");
const Question = require("./question.model");

// Required database setup and teardown
beforeAll(async () => await dbHandler.connect());
afterAll(async () => await dbHandler.closeDatabase());
const { setupDB } = require("../setup-test");
setupDB("question-model");

// Please add and expand on this simple test.
describe("Question Model saves the correct values", () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
const RecurringEvent = require("../../models/recurringEvent.model");
const dbHandler = require("../db-handler");
const RecurringEvent = require("./recurringEvent.model");

// Required database setup and teardown
beforeAll(async () => await dbHandler.connect());
afterAll(async () => await dbHandler.closeDatabase());
const { setupDB } = require("../setup-test");
setupDB("recurringEvent-model");

// Please add and expand on this simple test.
describe("Question Model saves the correct values", () => {
Expand Down
Loading