refactor invite to its own route and controller

This commit is contained in:
Alex Holliday committed 2024-08-26 17:43:56 -07:00
1 parent 3e63a32669
commit ac37211e32
10 files changed
+161 -35

No files matched your search

@@ -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
// {
+2 -2
View File
@@ -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,
});
}
+3 -3
View File
@@ -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,
+96
View File
@@ -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,
};
+30 -10
View File
@@ -2,15 +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,
teamId: req.body.teamId,
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) {
@@ -18,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);
+7 -12
View File
@@ -14,35 +14,30 @@ 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;
}
let teamId;
// Is user superadmin? If so, create team
// Handle creating team if superadmin
if (userData.role.includes("superadmin")) {
const team = new TeamModel({
email: userData.email,
});
teamId = team._id;
userData.teamId = team._id;
await team.save();
} else {
teamId = userData.teamId;
}
const newUser = new UserModel(userData);
newUser.teamId = teamId;
await newUser.save();
return await UserModel.findOne({ _id: newUser._id })
.select("-password")
+2
View File
@@ -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);
-5
View File
@@ -17,8 +17,6 @@ const {
checkSuperadminController,
getAllUsersController,
deleteUserController,
inviteController,
inviteVerifyController,
} = require("../controllers/authController");
//Auth routes
@@ -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);
+11
View File
@@ -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;
+2 -2
View File
@@ -39,7 +39,7 @@ const loginValidation = joi.object({
),
});
const registerValidation = joi.object({
const registrationBodyValidation = joi.object({
firstName: joi
.string()
.required()
@@ -336,7 +336,7 @@ const getMaintenanceWindowsByMonitorIdParamValidation = joi.object({
module.exports = {
roleValidatior,
loginValidation,
registerValidation,
registrationBodyValidation,
recoveryValidation,
recoveryTokenValidation,
newPasswordValidation,