From a822ae325421a15e983fb03a1fb04c248b51169f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Jul 2024 11:24:20 -0700 Subject: [PATCH 1/4] Add role to invite token --- Server/db/MongoDB.js | 2 ++ Server/models/InviteToken.js | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js index 104e49f38..438c7c306 100644 --- a/Server/db/MongoDB.js +++ b/Server/db/MongoDB.js @@ -46,6 +46,7 @@ const insertUser = async (req, res) => { const avatar = await GenerateAvatarImage(req.file); userData.avatarImage = avatar; } + console.log(userData); const newUser = new UserModel(userData); await newUser.save(); return await UserModel.findOne({ _id: newUser._id }) @@ -174,6 +175,7 @@ const requestInviteToken = async (req, res) => { await InviteToken.deleteMany({ email: req.body.email }); let inviteToken = new InviteToken({ email: req.body.email, + role: req.body.role, token: crypto.randomBytes(32).toString("hex"), }); await inviteToken.save(); diff --git a/Server/models/InviteToken.js b/Server/models/InviteToken.js index 6abc3d30b..e1e3413e9 100644 --- a/Server/models/InviteToken.js +++ b/Server/models/InviteToken.js @@ -7,6 +7,10 @@ const InviteTokenSchema = mongoose.Schema( required: true, unique: true, }, + role: { + type: String, + required: true, + }, token: { type: String, required: true, From c77414f2d7ea63257545fe48acc173c388ce5531 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Jul 2024 12:27:23 -0700 Subject: [PATCH 2/4] Added check to verify and fetch token from server --- Client/src/Pages/Auth/Register/Register.jsx | 21 +++++++++++++++++++-- Client/src/Validation/validation.js | 2 +- Server/controllers/authController.js | 17 +++++++++++++++++ Server/db/MongoDB.js | 16 ++++++++++++++++ Server/routes/authRoute.js | 2 ++ Server/utils/messages.js | 1 + Server/validation/joi.js | 5 +++++ 7 files changed, 61 insertions(+), 3 deletions(-) diff --git a/Client/src/Pages/Auth/Register/Register.jsx b/Client/src/Pages/Auth/Register/Register.jsx index a5db76373..662729eac 100644 --- a/Client/src/Pages/Auth/Register/Register.jsx +++ b/Client/src/Pages/Auth/Register/Register.jsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; import { Stack, Typography } from "@mui/material"; @@ -15,13 +15,13 @@ import { createToast } from "../../../Utils/toastUtils"; import Field from "../../../Components/Inputs/Field"; import { register } from "../../../Features/Auth/authSlice"; import { useParams } from "react-router-dom"; +import axiosInstance from "../../../Utils/axiosConfig"; const Register = ({ isAdmin }) => { const dispatch = useDispatch(); const navigate = useNavigate(); const { token } = useParams(); const theme = useTheme(); - // TODO If possible, change the IDs of these fields to match the backend const idMap = { "register-firstname-input": "firstName", @@ -41,6 +41,23 @@ const Register = ({ isAdmin }) => { }); const [errors, setErrors] = useState({}); + useEffect(() => { + const fetchInvite = async () => { + if (token !== undefined) { + try { + const res = await axiosInstance.post(`/auth/invite/verify`, { + token, + }); + const { role, email } = res.data.data; + setForm({ ...form, email, role }); + } catch (error) { + console.log(error); + } + } + }; + fetchInvite(); + }, [token]); + const handleSubmit = async (e) => { e.preventDefault(); diff --git a/Client/src/Validation/validation.js b/Client/src/Validation/validation.js index 00d5a1d49..a60259234 100644 --- a/Client/src/Validation/validation.js +++ b/Client/src/Validation/validation.js @@ -68,7 +68,7 @@ const credentials = joi.object({ } return value; }), - role: joi.array().required(), + role: joi.array(), }); const monitorValidation = joi.object({ diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index 28126c0a2..cec65eecd 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -10,6 +10,7 @@ const { deleteUserParamValidation, inviteRoleValidation, inviteBodyValidation, + inviteVerifciationBodyValidation, } = require("../validation/joi"); const logger = require("../utils/logger"); require("dotenv").config(); @@ -237,6 +238,21 @@ const inviteController = async (req, res, next) => { } }; +const inviteVerifyController = async (req, res, next) => { + try { + await inviteVerifciationBodyValidation.validateAsync(req.body); + const invite = await req.db.getInviteToken(req, res); + + res + .status(200) + .json({ status: "success", msg: "Invite verified", data: invite }); + } catch (error) { + error.service = SERVICE_NAME; + next(error); + return; + } +}; + /** * Checks to see if an admin account exists * @async @@ -445,6 +461,7 @@ module.exports = { loginController, userEditController, inviteController, + inviteVerifyController, checkAdminController, recoveryRequestController, validateRecoveryTokenController, diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js index 438c7c306..05b7ca4ab 100644 --- a/Server/db/MongoDB.js +++ b/Server/db/MongoDB.js @@ -185,6 +185,21 @@ const requestInviteToken = async (req, res) => { } }; +const getInviteToken = async (req, res) => { + try { + console.log(req.body.token); + const invite = await InviteToken.findOneAndDelete({ + token: req.body.token, + }); + if (invite === null) { + throw new Error(errorMessages.AUTH_INVITE_NOT_FOUND); + } + return invite; + } catch (error) { + throw error; + } +}; + /** * Request a recovery token * @async @@ -695,6 +710,7 @@ module.exports = { deleteUser, getAllUsers, requestInviteToken, + getInviteToken, requestRecoveryToken, validateRecoveryToken, resetPassword, diff --git a/Server/routes/authRoute.js b/Server/routes/authRoute.js index 02a1a67dd..137434b7a 100644 --- a/Server/routes/authRoute.js +++ b/Server/routes/authRoute.js @@ -17,6 +17,7 @@ const { getAllUsersController, deleteUserController, inviteController, + inviteVerifyController, } = require("../controllers/authController"); //Auth routes @@ -38,6 +39,7 @@ router.delete( ); router.post("/invite", verifyJWT, inviteController); +router.post("/invite/verify", inviteVerifyController); //Recovery routes router.post("/recovery/request", recoveryRequestController); diff --git a/Server/utils/messages.js b/Server/utils/messages.js index 9ef5c6938..19d9929ab 100644 --- a/Server/utils/messages.js +++ b/Server/utils/messages.js @@ -6,6 +6,7 @@ const errorMessages = { // Auth Controller UNAUTHORIZED: "Unauthorized access", AUTH_ADMIN_EXISTS: "Admin already exists", + AUTH_INVITE_NOT_FOUND: "Invite not found", //Error handling middleware UNKNOWN_SERVICE: "Unknown service", diff --git a/Server/validation/joi.js b/Server/validation/joi.js index ec773632c..eb298bb28 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -112,6 +112,10 @@ const inviteBodyValidation = joi.object({ }), }); +const inviteVerifciationBodyValidation = joi.object({ + token: joi.string().required(), +}); + //**************************************** // Monitors //**************************************** @@ -239,6 +243,7 @@ module.exports = { newPasswordValidation, inviteRoleValidation, inviteBodyValidation, + inviteVerifciationBodyValidation, getMonitorByIdValidation, getMonitorsByUserIdValidation, monitorValidation, From 608153b461ebb1fffefea16c2a7ecfbb5be29a11 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Jul 2024 14:30:27 -0700 Subject: [PATCH 3/4] Commented out admin check, unsure as to whether or not more than one admin can exist --- .../TabPanels/Account/TeamPanel.jsx | 2 +- Client/src/Pages/Auth/Register/Register.jsx | 4 +-- Server/controllers/authController.js | 29 ++++++++++--------- Server/db/MongoDB.js | 3 +- Server/models/InviteToken.js | 2 +- Server/validation/joi.js | 6 ++-- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Client/src/Components/TabPanels/Account/TeamPanel.jsx b/Client/src/Components/TabPanels/Account/TeamPanel.jsx index 77b8ba12b..5f3e501c5 100644 --- a/Client/src/Components/TabPanels/Account/TeamPanel.jsx +++ b/Client/src/Components/TabPanels/Account/TeamPanel.jsx @@ -185,7 +185,7 @@ const TeamPanel = () => { "/auth/invite", { email: toInvite.email, - role: [toInvite.role], + role: toInvite.role, }, { headers: { Authorization: `Bearer ${authToken}` } } ); diff --git a/Client/src/Pages/Auth/Register/Register.jsx b/Client/src/Pages/Auth/Register/Register.jsx index 662729eac..3b6b43306 100644 --- a/Client/src/Pages/Auth/Register/Register.jsx +++ b/Client/src/Pages/Auth/Register/Register.jsx @@ -49,6 +49,7 @@ const Register = ({ isAdmin }) => { token, }); const { role, email } = res.data.data; + console.log(role); setForm({ ...form, email, role }); } catch (error) { console.log(error); @@ -60,8 +61,7 @@ const Register = ({ isAdmin }) => { const handleSubmit = async (e) => { e.preventDefault(); - - const registerForm = { ...form, role: isAdmin ? ["admin"] : [] }; + const registerForm = { ...form, role: isAdmin ? ["admin"] : form.role }; const { error } = credentials.validate(registerForm, { abortEarly: false, context: { password: form.password }, diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index cec65eecd..88a7c7f71 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -59,20 +59,21 @@ const registerController = async (req, res, next) => { return; } - // Check if an admin user exists, if so, error - try { - const admin = await req.db.checkAdmin(req, res); - console.log(admin); - if (admin === true) { - throw new Error(errorMessages.AUTH_ADMIN_EXISTS); - } - } catch (error) { - console.log("WEEEEEEE", error.message); - error.service = SERVICE_NAME; - error.status = 403; - next(error); - return; - } + // TODO Can there be more than one admin? + // // Check if an admin user exists, if so, error + // try { + // const admin = await req.db.checkAdmin(req, res); + // console.log(admin); + // if (admin === true) { + // throw new Error(errorMessages.AUTH_ADMIN_EXISTS); + // } + // } catch (error) { + // console.log("WEEEEEEE", error.message); + // error.service = SERVICE_NAME; + // error.status = 403; + // next(error); + // return; + // } // Create a new user try { diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js index 05b7ca4ab..c3863ce36 100644 --- a/Server/db/MongoDB.js +++ b/Server/db/MongoDB.js @@ -172,6 +172,7 @@ const getAllUsers = async (req, res) => { const requestInviteToken = async (req, res) => { try { + console.log(req.body.role); await InviteToken.deleteMany({ email: req.body.email }); let inviteToken = new InviteToken({ email: req.body.email, @@ -188,7 +189,7 @@ const requestInviteToken = async (req, res) => { const getInviteToken = async (req, res) => { try { console.log(req.body.token); - const invite = await InviteToken.findOneAndDelete({ + const invite = await InviteToken.findOne({ token: req.body.token, }); if (invite === null) { diff --git a/Server/models/InviteToken.js b/Server/models/InviteToken.js index e1e3413e9..10515dadb 100644 --- a/Server/models/InviteToken.js +++ b/Server/models/InviteToken.js @@ -8,7 +8,7 @@ const InviteTokenSchema = mongoose.Schema( unique: true, }, role: { - type: String, + type: Array, required: true, }, token: { diff --git a/Server/validation/joi.js b/Server/validation/joi.js index eb298bb28..6384f13ba 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -68,7 +68,7 @@ const editUserBodyValidation = joi.object({ /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ ), deleteProfileImage: joi.boolean(), - role: joi.string(), + role: joi.array(), }); const recoveryValidation = joi.object({ @@ -107,9 +107,7 @@ const inviteBodyValidation = joi.object({ "string.empty": "Email is required", "string.email": "Must be a valid email address", }), - role: joi.string().required().messages({ - "string.empty": "Role is required", - }), + role: joi.array().required(), }); const inviteVerifciationBodyValidation = joi.object({ From aa6260af85d18a9a9072357e9f192f0366cb48d1 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 23 Jul 2024 14:37:15 -0700 Subject: [PATCH 4/4] use findOneAndDelete to make sure tokens are deleted when used --- Server/db/MongoDB.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js index c3863ce36..02447022e 100644 --- a/Server/db/MongoDB.js +++ b/Server/db/MongoDB.js @@ -172,7 +172,6 @@ const getAllUsers = async (req, res) => { const requestInviteToken = async (req, res) => { try { - console.log(req.body.role); await InviteToken.deleteMany({ email: req.body.email }); let inviteToken = new InviteToken({ email: req.body.email, @@ -188,8 +187,7 @@ const requestInviteToken = async (req, res) => { const getInviteToken = async (req, res) => { try { - console.log(req.body.token); - const invite = await InviteToken.findOne({ + const invite = await InviteToken.findOneAndDelete({ token: req.body.token, }); if (invite === null) {