diff --git a/Client/src/Components/Sidebar/index.jsx b/Client/src/Components/Sidebar/index.jsx
index 873ec8212..b3fd7669f 100644
--- a/Client/src/Components/Sidebar/index.jsx
+++ b/Client/src/Components/Sidebar/index.jsx
@@ -290,7 +290,7 @@ function Sidebar() {
if (
child.name === "Team" &&
authState.user?.role &&
- !authState.user.role.includes("admin")
+ !authState.user.role.includes("superadmin")
) {
return null;
}
@@ -350,7 +350,7 @@ function Sidebar() {
if (
child.name === "Team" &&
authState.user?.role &&
- !authState.user.role.includes("admin")
+ !authState.user.role.includes("superadmin")
) {
return null;
}
diff --git a/Client/src/Components/TabPanels/Account/TeamPanel.jsx b/Client/src/Components/TabPanels/Account/TeamPanel.jsx
index a0393977d..aa218e721 100644
--- a/Client/src/Components/TabPanels/Account/TeamPanel.jsx
+++ b/Client/src/Components/TabPanels/Account/TeamPanel.jsx
@@ -32,6 +32,12 @@ import Select from "../../Inputs/Select";
*/
const TeamPanel = () => {
+ const roleMap = {
+ superadmin: "Super admin",
+ admin: "Admin",
+ user: "Team member",
+ };
+
const theme = useTheme();
const { authToken, user } = useSelector((state) => state.auth);
@@ -78,6 +84,7 @@ const TeamPanel = () => {
// { id: 4, name: "ACTION" },
],
rows: team?.map((member, idx) => {
+ const roles = member.role.map((role) => roleMap[role]).join(",");
return {
id: member._id,
data: [
@@ -100,7 +107,7 @@ const TeamPanel = () => {
{
// TODO - Add select dropdown
id: idx + 2,
- data: member.role.includes("admin") ? "Administrator" : "Member",
+ data: roles,
},
// FEATURE STILL TO BE IMPLEMENTED
// {
diff --git a/Client/src/HOC/withAdminCheck.jsx b/Client/src/HOC/withAdminCheck.jsx
index 30a767f36..097f224e9 100644
--- a/Client/src/HOC/withAdminCheck.jsx
+++ b/Client/src/HOC/withAdminCheck.jsx
@@ -10,7 +10,7 @@ const withAdminCheck = (WrappedComponent) => {
useEffect(() => {
networkService
- .doesAdminExist()
+ .doesSuperAdminExist()
.then((response) => {
if (response.data.data === true) {
navigate("/login");
@@ -20,7 +20,7 @@ const withAdminCheck = (WrappedComponent) => {
logger.error(error);
});
}, [navigate]);
- return ;
+ return ;
};
const wrappedComponentName =
WrappedComponent.displayName || WrappedComponent.name || "Component";
diff --git a/Client/src/Pages/Account/index.jsx b/Client/src/Pages/Account/index.jsx
index 2fed926b8..e731d231a 100644
--- a/Client/src/Pages/Account/index.jsx
+++ b/Client/src/Pages/Account/index.jsx
@@ -25,7 +25,11 @@ const Account = ({ open = "profile" }) => {
let tabList = ["Profile", "Password", "Team"];
const { user } = useSelector((state) => state.auth);
- if (!user.role.includes("admin")) tabList = ["Profile", "Password"];
+ const requiredRoles = ["superadmin", "admin"];
+ const hideTeams = !requiredRoles.some((role) => user.role.includes(role));
+ if (hideTeams) {
+ tabList = ["Profile", "Password"];
+ }
return (
{
- {user.role.includes("admin") && }
+ {!hideTeams && }
);
diff --git a/Client/src/Pages/Auth/Login.jsx b/Client/src/Pages/Auth/Login.jsx
index b962fbb9a..30b7f8f8a 100644
--- a/Client/src/Pages/Auth/Login.jsx
+++ b/Client/src/Pages/Auth/Login.jsx
@@ -301,7 +301,7 @@ const Login = () => {
return;
}
networkService
- .doesAdminExist()
+ .doesSuperAdminExist()
.then((response) => {
if (response.data.data === false) {
navigate("/register");
diff --git a/Client/src/Pages/Auth/Register/Register.jsx b/Client/src/Pages/Auth/Register/Register.jsx
index 4680c1821..8163201ae 100644
--- a/Client/src/Pages/Auth/Register/Register.jsx
+++ b/Client/src/Pages/Auth/Register/Register.jsx
@@ -23,11 +23,11 @@ import { logger } from "../../../Utils/Logger";
* Displays the initial landing page.
*
* @param {Object} props
- * @param {boolean} props.isAdmin - Whether the user is creating and admin account
+ * @param {boolean} props.isSuperAdmin - Whether the user is creating and admin account
* @param {Function} props.onContinue - Callback function to handle "Continue with Email" button click.
* @returns {JSX.Element}
*/
-const LandingPage = ({ isAdmin, onSignup }) => {
+const LandingPage = ({ isSuperAdmin, onSignup }) => {
const theme = useTheme();
return (
@@ -40,7 +40,8 @@ const LandingPage = ({ isAdmin, onSignup }) => {
Sign Up
- Create your {isAdmin ? "admin " : ""}account to get started.
+ Create your {isSuperAdmin ? "Super admin " : ""}account to get
+ started.
@@ -93,7 +94,7 @@ const LandingPage = ({ isAdmin, onSignup }) => {
};
LandingPage.propTypes = {
- isAdmin: PropTypes.bool,
+ isSuperAdmin: PropTypes.bool,
onSignup: PropTypes.func,
};
@@ -422,7 +423,7 @@ StepThree.propTypes = {
onBack: PropTypes.func,
};
-const Register = ({ isAdmin }) => {
+const Register = ({ isSuperAdmin }) => {
const dispatch = useDispatch();
const navigate = useNavigate();
const { token } = useParams();
@@ -443,6 +444,7 @@ const Register = ({ isAdmin }) => {
password: "",
confirm: "",
role: [],
+ teamId: "",
});
const [errors, setErrors] = useState({});
const [step, setStep] = useState(0);
@@ -452,15 +454,16 @@ const Register = ({ isAdmin }) => {
if (token !== undefined) {
try {
const res = await networkService.verifyInvitationToken(token);
- const { role, email } = res.data.data;
- setForm({ ...form, email, role });
+ const invite = res.data.data;
+ const { role, email, teamId } = invite;
+ setForm({ ...form, email, role, teamId });
} catch (error) {
- logger.error(error);
+ navigate("/register", { replace: true });
}
}
};
fetchInvite();
- }, [token, form]);
+ }, []);
/**
* Validates the form data against the validation schema.
@@ -493,7 +496,6 @@ const Register = ({ isAdmin }) => {
const handleStepOne = async (e) => {
e.preventDefault();
-
let error = validateForm({
firstName: form.firstName,
lastName: form.lastName,
@@ -525,7 +527,10 @@ const Register = ({ isAdmin }) => {
const handleStepThree = async (e) => {
e.preventDefault();
- let registerForm = { ...form, role: isAdmin ? ["admin"] : form.role };
+ let registerForm = {
+ ...form,
+ role: isSuperAdmin ? ["superadmin"] : form.role,
+ };
let error = validateForm(registerForm, {
context: { password: form.password },
});
@@ -616,7 +621,10 @@ const Register = ({ isAdmin }) => {
}}
>
{step === 0 ? (
- setStep(1)} />
+ setStep(1)}
+ />
) : step === 1 ? (
{
);
};
Register.propTypes = {
- isAdmin: PropTypes.bool,
+ isSuperAdmin: PropTypes.bool,
};
export default Register;
diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js
index de4508cff..cd46469b4 100644
--- a/Client/src/Utils/NetworkService.js
+++ b/Client/src/Utils/NetworkService.js
@@ -293,8 +293,8 @@ class NetworkService {
* @returns {Promise} The response from the axios GET request.
*
*/
- async doesAdminExist() {
- return this.axiosInstance.get("/auth/users/admin");
+ async doesSuperAdminExist() {
+ return this.axiosInstance.get("/auth/users/superadmin");
}
/**
@@ -327,7 +327,7 @@ class NetworkService {
*/
async requestInvitationToken(authToken, email, role) {
return this.axiosInstance.post(
- `/auth/invite`,
+ `/invite`,
{ email, role },
{
headers: { Authorization: `Bearer ${authToken}` },
@@ -346,7 +346,7 @@ class NetworkService {
*
*/
async verifyInvitationToken(token) {
- return this.axiosInstance.post(`/auth/invite/verify`, {
+ return this.axiosInstance.post(`/invite/verify`, {
token,
});
}
diff --git a/Client/src/Validation/validation.js b/Client/src/Validation/validation.js
index 6aa7107b6..64c075ec4 100644
--- a/Client/src/Validation/validation.js
+++ b/Client/src/Validation/validation.js
@@ -76,6 +76,7 @@ const credentials = joi.object({
return value;
}),
role: joi.array(),
+ teamId: joi.string().allow("").optional(),
});
const monitorValidation = joi.object({
diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js
index 6fbcdb669..03f7f1b66 100644
--- a/Server/controllers/authController.js
+++ b/Server/controllers/authController.js
@@ -1,6 +1,6 @@
const express = require("express");
const {
- registerValidation,
+ registrationBodyValidation,
loginValidation,
editUserParamValidation,
editUserBodyValidation,
@@ -50,7 +50,7 @@ const getTokenFromHeaders = (headers) => {
const registerController = async (req, res, next) => {
// joi validation
try {
- await registerValidation.validateAsync(req.body);
+ await registrationBodyValidation.validateAsync(req.body);
} catch (error) {
error.status = 422;
error.service = SERVICE_NAME;
@@ -78,7 +78,7 @@ const registerController = async (req, res, next) => {
// Create a new user
try {
- const newUser = await req.db.insertUser(req, res);
+ const newUser = await req.db.insertUser({ ...req.body }, req.file);
logger.info(successMessages.AUTH_CREATE_USER, {
service: SERVICE_NAME,
userId: newUser._id,
@@ -90,12 +90,20 @@ const registerController = async (req, res, next) => {
const token = issueToken(userForToken);
- await req.emailService.buildAndSendEmail(
- "welcomeEmailTemplate",
- { name: newUser.firstName },
- newUser.email,
- "Welcome to Uptime Monitor"
- );
+ req.emailService
+ .buildAndSendEmail(
+ "welcomeEmailTemplate",
+ { name: newUser.firstName },
+ newUser.email,
+ "Welcome to Uptime Monitor"
+ )
+ .catch((error) => {
+ logger.error("Error sending welcome email", {
+ service: SERVICE_NAME,
+ error: error.message,
+ });
+ });
+
return res.status(200).json({
success: true,
msg: successMessages.AUTH_CREATE_USER,
@@ -226,7 +234,8 @@ const inviteController = async (req, res, next) => {
try {
// Only admins can invite
const token = getTokenFromHeaders(req.headers);
- const { role, firstname } = jwt.decode(token);
+ const { role, firstname, teamId } = jwt.decode(token);
+ req.body.teamId = teamId;
try {
await inviteRoleValidation.validateAsync({ roles: role });
await inviteBodyValidation.validateAsync(req.body);
@@ -240,15 +249,22 @@ const inviteController = async (req, res, next) => {
}
const inviteToken = await req.db.requestInviteToken(req, res);
- await req.emailService.buildAndSendEmail(
- "employeeActivationTemplate",
- {
- name: firstname,
- link: `${process.env.CLIENT_HOST}/register/${inviteToken.token}`,
- },
- req.body.email,
- "Welcome to Uptime Monitor"
- );
+ req.emailService
+ .buildAndSendEmail(
+ "employeeActivationTemplate",
+ {
+ name: firstname,
+ link: `${process.env.CLIENT_HOST}/register/${inviteToken.token}`,
+ },
+ req.body.email,
+ "Welcome to Uptime Monitor"
+ )
+ .catch((error) => {
+ logger.error("Error sending invite email", {
+ service: SERVICE_NAME,
+ error: error.message,
+ });
+ });
return res
.status(200)
@@ -274,7 +290,6 @@ const inviteVerifyController = async (req, res, next) => {
try {
const invite = await req.db.getInviteToken(req, res);
-
res
.status(200)
.json({ status: "success", msg: "Invite verified", data: invite });
@@ -293,13 +308,13 @@ const inviteVerifyController = async (req, res, next) => {
* @returns {Promise}
*/
-const checkAdminController = async (req, res) => {
+const checkSuperadminController = async (req, res) => {
try {
- const adminExists = await req.db.checkAdmin(req, res);
+ const superAdminExists = await req.db.checkSuperadmin(req, res);
return res.status(200).json({
success: true,
msg: successMessages.AUTH_ADMIN_EXISTS,
- data: adminExists,
+ data: superAdminExists,
});
} catch (error) {
error.service = SERVICE_NAME;
@@ -508,7 +523,7 @@ module.exports = {
userEditController,
inviteController,
inviteVerifyController,
- checkAdminController,
+ checkSuperadminController,
recoveryRequestController,
validateRecoveryTokenController,
resetPasswordController,
diff --git a/Server/controllers/inviteController.js b/Server/controllers/inviteController.js
new file mode 100644
index 000000000..e685d0eef
--- /dev/null
+++ b/Server/controllers/inviteController.js
@@ -0,0 +1,96 @@
+const {
+ inviteRoleValidation,
+ inviteBodyValidation,
+ inviteVerifciationBodyValidation,
+} = require("../validation/joi");
+const logger = require("../utils/logger");
+require("dotenv").config();
+const { errorMessages, successMessages } = require("../utils/messages");
+var jwt = require("jsonwebtoken");
+const SERVICE_NAME = "inviteController";
+
+const getTokenFromHeaders = (headers) => {
+ const authorizationHeader = headers.authorization;
+ if (!authorizationHeader) throw new Error("No auth headers");
+
+ const parts = authorizationHeader.split(" ");
+ if (parts.length !== 2 || parts[0] !== "Bearer")
+ throw new Error("Invalid auth headers");
+
+ return parts[1];
+};
+
+const inviteController = async (req, res, next) => {
+ try {
+ // Only admins can invite
+ const token = getTokenFromHeaders(req.headers);
+ const { role, firstname, teamId } = jwt.decode(token);
+ req.body.teamId = teamId;
+ try {
+ await inviteRoleValidation.validateAsync({ roles: role });
+ await inviteBodyValidation.validateAsync(req.body);
+ } catch (error) {
+ error.status = 422;
+ error.service = SERVICE_NAME;
+ error.message =
+ error.details?.[0]?.message || error.message || "Validation Error";
+ next(error);
+ return;
+ }
+
+ const inviteToken = await req.db.requestInviteToken({ ...req.body });
+ req.emailService
+ .buildAndSendEmail(
+ "employeeActivationTemplate",
+ {
+ name: firstname,
+ link: `${process.env.CLIENT_HOST}/register/${inviteToken.token}`,
+ },
+ req.body.email,
+ "Welcome to Uptime Monitor"
+ )
+ .catch((error) => {
+ logger.error("Error sending invite email", {
+ service: SERVICE_NAME,
+ error: error.message,
+ });
+ });
+
+ return res
+ .status(200)
+ .json({ success: true, msg: "Invite sent", data: inviteToken });
+ } catch (error) {
+ error.service = SERVICE_NAME;
+ next(error);
+ return;
+ }
+};
+
+const inviteVerifyController = async (req, res, next) => {
+ try {
+ await inviteVerifciationBodyValidation.validateAsync(req.body);
+ } catch (error) {
+ error.status = 422;
+ error.service = SERVICE_NAME;
+ error.message =
+ error.details?.[0]?.message || error.message || "Validation Error";
+ next(error);
+ return;
+ }
+
+ try {
+ const invite = await req.db.getInviteToken(req.body.token);
+ res
+ .status(200)
+ .json({ status: "success", msg: "Invite verified", data: invite });
+ } catch (error) {
+ error.service = SERVICE_NAME;
+ next(error);
+ return;
+ }
+};
+
+module.exports = {
+ inviteController,
+ inviteVerifyController,
+};
diff --git a/Server/db/mongo/MongoDB.js b/Server/db/mongo/MongoDB.js
index 9c1005c32..d6440ca12 100644
--- a/Server/db/mongo/MongoDB.js
+++ b/Server/db/mongo/MongoDB.js
@@ -15,10 +15,10 @@ const connect = async () => {
}
};
-const checkAdmin = async (req, res) => {
+const checkSuperadmin = async (req, res) => {
try {
- const admin = await UserModel.findOne({ role: "admin" });
- if (admin !== null) {
+ const superAdmin = await UserModel.findOne({ role: "superadmin" });
+ if (superAdmin !== null) {
return true;
}
return false;
@@ -141,7 +141,7 @@ module.exports = {
requestRecoveryToken,
validateRecoveryToken,
resetPassword,
- checkAdmin,
+ checkSuperadmin,
getAllMonitors,
getMonitorStatsById,
getMonitorById,
diff --git a/Server/db/mongo/modules/inviteModule.js b/Server/db/mongo/modules/inviteModule.js
index b4404c615..975f6f1e8 100644
--- a/Server/db/mongo/modules/inviteModule.js
+++ b/Server/db/mongo/modules/inviteModule.js
@@ -2,14 +2,25 @@ const InviteToken = require("../../../models/InviteToken");
const crypto = require("crypto");
const { errorMessages } = require("../../../utils/messages");
-const requestInviteToken = async (req, res) => {
+/**
+ * Request an invite token for a user.
+ *
+ * This function deletes any existing invite tokens for the user's email,
+ * generates a new token, saves it, and then returns the new token.
+ *
+ * @param {Object} userData - The user data.
+ * @param {string} userData.email - The user's email.
+ * @param {mongoose.Schema.Types.ObjectId} userData.teamId - The ID of the team.
+ * @param {Array} userData.role - The user's role(s).
+ * @param {Date} [userData.expiry=Date.now] - The expiry date of the token. Defaults to the current date and time.
+ * @returns {Promise} The invite token.
+ * @throws {Error} If there is an error.
+ */
+const requestInviteToken = async (userData) => {
try {
- 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.deleteMany({ email: userData.email });
+ userData.token = crypto.randomBytes(32).toString("hex");
+ let inviteToken = new InviteToken(userData);
await inviteToken.save();
return inviteToken;
} catch (error) {
@@ -17,10 +28,20 @@ const requestInviteToken = async (req, res) => {
}
};
-const getInviteToken = async (req, res) => {
+/**
+ * Retrieves and deletes an invite token.
+ *
+ * This function searches for an invite token in the database and deletes it.
+ * If the invite token is not found, it throws an error.
+ *
+ * @param {string} token - The invite token to search for.
+ * @returns {Promise} The invite token data.
+ * @throws {Error} If the invite token is not found or there is another error.
+ */
+const getInviteToken = async (token) => {
try {
const invite = await InviteToken.findOneAndDelete({
- token: req.body.token,
+ token,
});
if (invite === null) {
throw new Error(errorMessages.AUTH_INVITE_NOT_FOUND);
diff --git a/Server/db/mongo/modules/userModule.js b/Server/db/mongo/modules/userModule.js
index 3dacfd943..d29c1889b 100644
--- a/Server/db/mongo/modules/userModule.js
+++ b/Server/db/mongo/modules/userModule.js
@@ -1,4 +1,5 @@
const UserModel = require("../../../models/user");
+const TeamModel = require("../../../models/Team");
const { errorMessages } = require("../../../utils/messages");
const { GenerateAvatarImage } = require("../../../utils/imageProcessing");
@@ -13,20 +14,29 @@ const { ParseBoolean } = require("../../../utils/utils");
* @returns {Promise}
* @throws {Error}
*/
-const insertUser = async (req, res) => {
+const insertUser = async (userData, imageFile) => {
try {
- const userData = { ...req.body };
- if (req.file) {
+ if (imageFile) {
// 1. Save the full size image
userData.profileImage = {
- data: req.file.buffer,
- contentType: req.file.mimetype,
+ data: imageFile.buffer,
+ contentType: imageFile.mimetype,
};
// 2. Get the avatar sized image
- const avatar = await GenerateAvatarImage(req.file);
+ const avatar = await GenerateAvatarImage(imageFile);
userData.avatarImage = avatar;
}
+
+ // Handle creating team if superadmin
+ if (userData.role.includes("superadmin")) {
+ const team = new TeamModel({
+ email: userData.email,
+ });
+ userData.teamId = team._id;
+ await team.save();
+ }
+
const newUser = new UserModel(userData);
await newUser.save();
return await UserModel.findOne({ _id: newUser._id })
diff --git a/Server/index.js b/Server/index.js
index 9a5f1797f..846b11e3d 100644
--- a/Server/index.js
+++ b/Server/index.js
@@ -7,6 +7,7 @@ const { verifyJWT } = require("./middleware/verifyJWT");
const { handleErrors } = require("./middleware/handleErrors");
const { errorMessages } = require("./utils/messages");
const authRouter = require("./routes/authRoute");
+const inviteRouter = require("./routes/inviteRoute");
const monitorRouter = require("./routes/monitorRoute");
const checkRouter = require("./routes/checkRoute");
const alertRouter = require("./routes/alertRoute");
@@ -71,6 +72,7 @@ const startApp = async () => {
//routes
app.use("/api/v1/auth", authRouter);
+ app.use("/api/v1/invite", inviteRouter);
app.use("/api/v1/monitors", verifyJWT, monitorRouter);
app.use("/api/v1/checks", verifyJWT, checkRouter);
app.use("/api/v1/alerts", verifyJWT, alertRouter);
diff --git a/Server/middleware/verifyAdmin.js b/Server/middleware/verifySuperAdmin.js
similarity index 91%
rename from Server/middleware/verifyAdmin.js
rename to Server/middleware/verifySuperAdmin.js
index 7ef123451..82401b3b7 100644
--- a/Server/middleware/verifyAdmin.js
+++ b/Server/middleware/verifySuperAdmin.js
@@ -11,7 +11,7 @@ const { errorMessages } = require("../utils/messages");
* @param {express.NextFunction} next
* @returns {express.Response}
*/
-const verifyAdmin = (req, res, next) => {
+const verifySuperAdmin = (req, res, next) => {
const token = req.headers["authorization"];
// Make sure a token is provided
if (!token) {
@@ -42,7 +42,7 @@ const verifyAdmin = (req, res, next) => {
.json({ success: false, msg: errorMessages.INVALID_AUTH_TOKEN });
}
- if (decoded.role.includes("admin") === false) {
+ if (decoded.role.includes("superadmin") === false) {
logger.error(errorMessages.INVALID_AUTH_TOKEN, {
service: SERVICE_NAME,
});
@@ -54,4 +54,4 @@ const verifyAdmin = (req, res, next) => {
});
};
-module.exports = { verifyAdmin };
+module.exports = { verifySuperAdmin };
diff --git a/Server/models/InviteToken.js b/Server/models/InviteToken.js
index 10515dadb..5fbd4fc73 100644
--- a/Server/models/InviteToken.js
+++ b/Server/models/InviteToken.js
@@ -7,6 +7,12 @@ const InviteTokenSchema = mongoose.Schema(
required: true,
unique: true,
},
+ teamId: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: "Team",
+ immutable: true,
+ required: true,
+ },
role: {
type: Array,
required: true,
diff --git a/Server/models/Team.js b/Server/models/Team.js
new file mode 100644
index 000000000..25e1f9b22
--- /dev/null
+++ b/Server/models/Team.js
@@ -0,0 +1,16 @@
+const mongoose = require("mongoose");
+
+const TeamSchema = mongoose.Schema(
+ {
+ email: {
+ type: String,
+ required: true,
+ unique: true,
+ },
+ },
+ {
+ timestamps: true,
+ }
+);
+
+module.exports = mongoose.model("Team", TeamSchema);
diff --git a/Server/models/user.js b/Server/models/user.js
index e05e74037..829129b4d 100644
--- a/Server/models/user.js
+++ b/Server/models/user.js
@@ -38,7 +38,12 @@ const UserSchema = mongoose.Schema(
role: {
type: [String],
default: "user",
- enum: ["user", "admin"],
+ enum: ["user", "admin", "superadmin"],
+ },
+ teamId: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: "Team",
+ immutable: true,
},
},
{
diff --git a/Server/routes/authRoute.js b/Server/routes/authRoute.js
index 201807121..ddecd8d39 100644
--- a/Server/routes/authRoute.js
+++ b/Server/routes/authRoute.js
@@ -1,6 +1,6 @@
const router = require("express").Router();
const { verifyJWT } = require("../middleware/verifyJWT");
-const { verifyAdmin } = require("../middleware/verifyAdmin");
+const { verifySuperAdmin } = require("../middleware/verifySuperAdmin");
const { verifyOwnership } = require("../middleware/verifyOwnership");
const multer = require("multer");
const upload = multer();
@@ -14,11 +14,9 @@ const {
recoveryRequestController,
validateRecoveryTokenController,
resetPasswordController,
- checkAdminController,
+ checkSuperadminController,
getAllUsersController,
deleteUserController,
- inviteController,
- inviteVerifyController,
} = require("../controllers/authController");
//Auth routes
@@ -30,8 +28,8 @@ router.put(
verifyJWT,
userEditController
);
-router.get("/users/admin", checkAdminController);
-router.get("/users", verifyJWT, verifyAdmin, getAllUsersController);
+router.get("/users/superadmin", checkSuperadminController);
+router.get("/users", verifyJWT, verifySuperAdmin, getAllUsersController);
router.delete(
"/user/:userId",
verifyJWT,
@@ -39,9 +37,6 @@ router.delete(
deleteUserController
);
-router.post("/invite", verifyJWT, inviteController);
-router.post("/invite/verify", inviteVerifyController);
-
//Recovery routes
router.post("/recovery/request", recoveryRequestController);
router.post("/recovery/validate", validateRecoveryTokenController);
diff --git a/Server/routes/inviteRoute.js b/Server/routes/inviteRoute.js
new file mode 100644
index 000000000..05df2f51f
--- /dev/null
+++ b/Server/routes/inviteRoute.js
@@ -0,0 +1,11 @@
+const router = require("express").Router();
+const { verifyJWT } = require("../middleware/verifyJWT");
+const {
+ inviteController,
+ inviteVerifyController,
+} = require("../controllers/inviteController");
+
+router.post("/", verifyJWT, inviteController);
+router.post("/verify", inviteVerifyController);
+
+module.exports = router;
diff --git a/Server/validation/joi.js b/Server/validation/joi.js
index 4f8042e12..a9bcb9683 100644
--- a/Server/validation/joi.js
+++ b/Server/validation/joi.js
@@ -5,8 +5,11 @@ const joi = require("joi");
//****************************************
const roleValidatior = (role) => (value, helpers) => {
- if (!value.includes(role)) {
- throw new joi.ValidationError(`You do not have ${role} authorization`);
+ const hasRole = role.some((role) => value.includes(role));
+ if (!hasRole) {
+ throw new Joi.ValidationError(
+ `You do not have the required authorization. Required roles: ${roles.join(", ")}`
+ );
}
return value;
};
@@ -36,7 +39,7 @@ const loginValidation = joi.object({
),
});
-const registerValidation = joi.object({
+const registrationBodyValidation = joi.object({
firstName: joi
.string()
.required()
@@ -66,9 +69,10 @@ const registerValidation = joi.object({
profileImage: joi.any(),
role: joi
.array()
- .items(joi.string().valid("admin", "user"))
+ .items(joi.string().valid("superadmin", "admin", "user"))
.min(1)
.required(),
+ teamId: joi.string().allow("").required(),
});
const editUserParamValidation = joi.object({
@@ -123,7 +127,7 @@ const deleteUserParamValidation = joi.object({
});
const inviteRoleValidation = joi.object({
- roles: joi.custom(roleValidatior("admin")).required(),
+ roles: joi.custom(roleValidatior(["admin", "superadmin"])).required(),
});
const inviteBodyValidation = joi.object({
@@ -132,6 +136,7 @@ const inviteBodyValidation = joi.object({
"string.email": "Must be a valid email address",
}),
role: joi.array().required(),
+ teamId: joi.string().required(),
});
const inviteVerifciationBodyValidation = joi.object({
@@ -331,7 +336,7 @@ const getMaintenanceWindowsByMonitorIdParamValidation = joi.object({
module.exports = {
roleValidatior,
loginValidation,
- registerValidation,
+ registrationBodyValidation,
recoveryValidation,
recoveryTokenValidation,
newPasswordValidation,