Merge pull request #728 from bluewave-labs/feat/team-table
Feat/team table, #725
This commit is contained in:
21 files changed
+289
-87
No files matched your search
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
// {
|
||||
|
||||
@@ -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 <WrappedComponent {...props} isAdmin={true} />;
|
||||
return <WrappedComponent {...props} isSuperAdmin={true} />;
|
||||
};
|
||||
const wrappedComponentName =
|
||||
WrappedComponent.displayName || WrappedComponent.name || "Component";
|
||||
|
||||
@@ -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 (
|
||||
<Box
|
||||
@@ -71,7 +75,7 @@ const Account = ({ open = "profile" }) => {
|
||||
</Box>
|
||||
<ProfilePanel />
|
||||
<PasswordPanel />
|
||||
{user.role.includes("admin") && <TeamPanel />}
|
||||
{!hideTeams && <TeamPanel />}
|
||||
</TabContext>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -301,7 +301,7 @@ const Login = () => {
|
||||
return;
|
||||
}
|
||||
networkService
|
||||
.doesAdminExist()
|
||||
.doesSuperAdminExist()
|
||||
.then((response) => {
|
||||
if (response.data.data === false) {
|
||||
navigate("/register");
|
||||
|
||||
@@ -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 }) => {
|
||||
<Box>
|
||||
<Typography component="h1">Sign Up</Typography>
|
||||
<Typography>
|
||||
Create your {isAdmin ? "admin " : ""}account to get started.
|
||||
Create your {isSuperAdmin ? "Super admin " : ""}account to get
|
||||
started.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box width="100%">
|
||||
@@ -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 ? (
|
||||
<LandingPage isAdmin={isAdmin} onSignup={() => setStep(1)} />
|
||||
<LandingPage
|
||||
isSuperAdmin={isSuperAdmin}
|
||||
onSignup={() => setStep(1)}
|
||||
/>
|
||||
) : step === 1 ? (
|
||||
<StepOne
|
||||
form={form}
|
||||
@@ -664,6 +672,6 @@ const Register = ({ isAdmin }) => {
|
||||
);
|
||||
};
|
||||
Register.propTypes = {
|
||||
isAdmin: PropTypes.bool,
|
||||
isSuperAdmin: PropTypes.bool,
|
||||
};
|
||||
export default Register;
|
||||
@@ -293,8 +293,8 @@ class NetworkService {
|
||||
* @returns {Promise<AxiosResponse>} 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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ const credentials = joi.object({
|
||||
return value;
|
||||
}),
|
||||
role: joi.array(),
|
||||
teamId: joi.string().allow("").optional(),
|
||||
});
|
||||
|
||||
const monitorValidation = joi.object({
|
||||
|
||||
@@ -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<Express.Response>}
|
||||
*/
|
||||
|
||||
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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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<InviteToken>} 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<InviteToken>} 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);
|
||||
|
||||
@@ -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<UserModel>}
|
||||
* @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 })
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 };
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user