diff --git a/.gitignore b/.gitignore index c86c332..4dd37d9 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,5 @@ WWebJS npm-debug.log* yarn-debug.log* -yarn-error.log* \ No newline at end of file +yarn-error.log* + diff --git a/backend/src/controllers/IAMControllerEL.ts b/backend/src/controllers/IAMControllerEL.ts new file mode 100644 index 0000000..c3cd8b7 --- /dev/null +++ b/backend/src/controllers/IAMControllerEL.ts @@ -0,0 +1,494 @@ +import { Request, Response } from "express"; +import { getIO } from "../libs/socket"; +import { Op } from "sequelize"; +import CreateUserService from "../services/UserServices/CreateUserService"; +import UpdateUserService from "../services/UserServices/UpdateUserService"; +import DeleteUserService from "../services/UserServices/DeleteUserService"; +import { del, get, set } from "../helpers/RedisClient"; + +import { + startWhoIsOnlineMonitor, + stopWhoIsOnlineMonitor +} from "../helpers/WhoIsOnlineMonitor"; + +import User from "../models/User"; + +export const createUser = async ( + req: Request, + res: Response +): Promise => { + const { user_id, user_first_name, user_tax_id, user_email, user_title }: any = + req.body; + + const invalid = invalidProperties(req.body, [ + "user_id", + "user_tax_id", + "user_first_name" + ]); + + if (invalid) { + return res.status(400).json(response("1", `${invalid}`, "0", "createUser")); + } + + const auxUser = await User.findOne({ where: { secondaryId: user_id } }); + + if (auxUser) { + return res + .status(400) + .json( + response("1", `The user ${user_id} already exist`, "0", "createUser") + ); + } + + const user = await CreateUserService({ + email: user_tax_id || user_email, + password: "12345", + name: user_first_name, + positionCompany: user_title, + profile: "user", + ignoreThrow: true + }); + + if (user?.error) { + return res + .status(user?.status) + .json(response("0", `${user?.msg}`, "0", "createUser")); + } + + if (!user?.error) { + const _user = await User.findByPk(user.id); + _user?.update({ secondaryId: user_id }); + + const { id, name } = user; + await set(`user:${id}`, { id, name }); + + const io = getIO(); + io.emit("user", { + action: "create", + user + }); + + await startWhoIsOnlineMonitor(); + } + + return res + .status(200) + .json(response("1", `User ${user_id} created`, "1", "createUser")); +}; + +export const deleteUser = async ( + req: Request, + res: Response +): Promise => { + const { user_id }: any = req.body; + + const invalid = invalidProperties(req.body, ["user_id"]); + + if (invalid) { + return res.status(400).json(response("1", `${invalid}`, "0", "deleteUser")); + } + + const _user = await User.findOne({ where: { secondaryId: user_id } }); + + if (_user) { + const user = await DeleteUserService(_user.id, true); + + if (user?.error) { + return res + .status(user?.status) + .json(response("0", `${user?.msg}`, "0", "deleteUser")); + } + + if (!user?.error) { + del(`user:${_user.id}`); + + const io = getIO(); + io.emit("user", { + action: "delete", + userId: _user.id + }); + + await stopWhoIsOnlineMonitor(); + + io.emit("onlineStatus", { + action: "delete", + userOnlineTime: _user.id + }); + + await startWhoIsOnlineMonitor(); + + return res + .status(200) + .json(response("1", `User ${user_id} deleted`, "1", "deleteUser")); + } + } + + return res + .status(500) + .json(response("0", "Internal server error", "0", "deleteUser")); +}; + +export const listAllUsers = async ( + req: Request, + res: Response +): Promise => { + const _users: any = await User.findAll({ + where: { + secondaryId: { + [Op.ne]: "" + } + }, + attributes: ["secondaryId", "name"] + }); + + if (_users) { + const user_list = _users.map((user: any) => { + const { secondaryId, name } = user; + return { user_id: secondaryId, full_name: name }; + }); + + return res + .status(200) + .json(response("1", "Success", user_list, "listAllUsers")); + } + + return res + .status(500) + .json(response("0", "Internal server error", [], "listAllUsers")); +}; + +export const checkUser = async ( + req: Request, + res: Response +): Promise => { + const { user_id }: any = req.body; + + const invalid = invalidProperties(req.body, ["user_id"]); + + if (invalid) { + return res.status(400).json(response("1", `${invalid}`, "0", "checkUser")); + } + + const _user = await User.findOne({ where: { secondaryId: user_id } }); + + if (_user) { + return res + .status(200) + .json(response("1", `User ${user_id} exist`, "1", "checkUser")); + } + + return res + .status(404) + .json(response("1", `User ${user_id} not exist`, "0", "checkUser")); +}; + +export const updateUser = async ( + req: Request, + res: Response +): Promise => { + const { user_id, user_first_name, user_tax_id, user_email, user_title }: any = + req.body; + + const invalid = invalidProperties(req.body, ["user_id"]); + + if (invalid) { + return res.status(400).json(response("1", `${invalid}`, "0", "checkUser")); + } + + const _user: any = await User.findOne({ where: { secondaryId: user_id } }); + + if (!_user) + return res + .status(404) + .json(response("1", `User ${user_id} not exist`, "0", "updateUser")); + + const userData = { + email: user_tax_id || user_email, + name: user_first_name, + positionCompany: user_title + }; + + let user: any = await UpdateUserService({ + userData, + userId: _user.id, + ignoreThrow: true + }); + + if (user?.error) { + return res + .status(user?.status) + .json(response("0", `${user?.msg}`, "0", "updateUser")); + } + + if (user) { + const { id, name } = user; + await set(`user:${id}`, { id, name }); + } + + const io = getIO(); + io.emit("user", { + action: "update", + user + }); + + return res + .status(200) + .json(response("1", `User ${user_id} updated`, "1", "updateUser")); +}; + +export const resetPassword = async ( + req: Request, + res: Response +): Promise => { + const { user_id, user_password }: any = req.body; + + const invalid = invalidProperties(req.body, ["user_id", "user_password"]); + + if (invalid) { + return res + .status(400) + .json(response("1", `${invalid}`, "0", "resetPassword")); + } + + const _user = await User.findOne({ where: { secondaryId: user_id } }); + + if (!_user) { + return res + .status(404) + .json(response("1", `User ${user_id} not exist`, "0", "resetPassword")); + } + + const userData = { + password: user_password, + email: _user.email + }; + + let user: any = await UpdateUserService({ + userData, + userId: _user.id, + ignoreThrow: true + }); + + if (user?.error) { + return res + .status(user?.status) + .json(response("0", `${user?.msg}`, "0", "resetPassword")); + } + + await logoutUser(_user.id); + + return res + .status(200) + .json( + response("1", `User ${user_id} password updated`, "1", "resetPassword") + ); +}; + +export const linkUserAndUserRight = async ( + req: Request, + res: Response +): Promise => { + const { user_id, user_right_id, user_right_title }: any = req.body; + + const invalid = invalidProperties(req.body, ["user_id", "user_right_id"]); + + if (invalid) { + return res + .status(400) + .json(response("1", `${invalid}`, "0", "linkUserAndUserRight")); + } + + if ( + (user_right_id && + !["admin", "user", "supervisor"].includes( + user_right_id?.trim().toLocaleLowerCase() + )) || + (user_right_title && + !["admin", "user", "supervisor"].includes( + user_right_title?.trim().toLocaleLowerCase() + )) + ) { + return res + .status(400) + .json( + response( + "1", + `The user profile ${ + user_right_title || user_right_id + } provided by the property user_right_title or user_right_id does not match the following profiles: admin, user, supervisor`, + "0", + "linkUserAndUserRight" + ) + ); + } + + const _user: any = await User.findOne({ where: { secondaryId: user_id } }); + + if (!_user) + return res + .status(404) + .json( + response("1", `User ${user_id} not exist`, "0", "linkUserAndUserRight") + ); + + const userData = { + profile: user_right_title || user_right_id, + email: _user.email + }; + + let user: any = await UpdateUserService({ + userData, + userId: _user.id, + ignoreThrow: true + }); + + if (user?.error) { + return res + .status(user?.status) + .json(response("0", `${user?.msg}`, "0", "linkUserAndUserRight")); + } + + await logoutUser(_user.id); + + return res + .status(200) + .json( + response( + "1", + `User ${user_id} associated with ${ + user_right_title || user_right_id + } profile`, + "1", + "linkUserAndUserRight" + ) + ); +}; + +export const checkUserRight = async ( + req: Request, + res: Response +): Promise => { + const { user_id, user_right_id, user_right_title }: any = req.body; + + const invalid = invalidProperties(req.body, ["user_id", "user_right_id"]); + + if (invalid) { + return res + .status(400) + .json(response("1", `${invalid}`, "0", "checkUserRight")); + } + + if ( + (user_right_id && + !["admin", "user", "supervisor"].includes( + user_right_id?.trim().toLocaleLowerCase() + )) || + (user_right_title && + !["admin", "user", "supervisor"].includes( + user_right_title?.trim().toLocaleLowerCase() + )) + ) { + return res + .status(400) + .json( + response( + "1", + `The user profile ${ + user_right_title || user_right_id + } provided by the property user_right_title or user_right_id does not match the following profiles: admin, user, supervisor`, + "0", + "checkUserRight" + ) + ); + } + + const _user: any = await User.findOne({ + where: { + secondaryId: user_id + } + }); + + if (!_user) + return res + .status(404) + .json(response("1", `User ${user_id} not exist`, "0", "checkUserRight")); + + if ( + (user_right_id && _user.profile != user_right_id) || + (user_right_title && _user.profile != user_right_title) + ) { + return res + .status(403) + .json( + response( + "1", + `User ${user_id} does not have this profile`, + "0", + "checkUserRight" + ) + ); + } + + return res + .status(200) + .json( + response( + "1", + `User ${user_id} has ${user_right_title || user_right_id} profile`, + "1", + "checkUserRight" + ) + ); +}; + +async function logoutUser(userId: any) { + await stopWhoIsOnlineMonitor(); + + let onlineTime = { + userId: `${userId}`, + status: "logout..." + }; + + const io = getIO(); + io.emit("onlineStatus", { + action: "logout", + userOnlineTime: onlineTime + }); + + await startWhoIsOnlineMonitor(); +} + +function response(code: string, msg: string, obj: any, type: string) { + let payload = { return_code: code, return_msg: msg }; + + switch (type) { + case "createUser": + return { ...payload, user_created: obj }; + case "deleteUser": + return { ...payload, user_removed: obj }; + case "listAllUsers": + return { ...payload, user_list: obj }; + case "checkUser": + return { ...payload, user_exists: obj }; + case "updateUser": + return { ...payload, user_updated: obj }; + case "resetPassword": + return { ...payload, password_set: obj }; + case "linkUserAndUserRight": + return { ...payload, user_right_linked: obj }; + case "checkUserRight": + return { ...payload, user_right_exists: obj }; + default: + return payload; + } +} + +function invalidProperties(body: any, pros: any[]) { + for (const field of pros) { + console.log("body[field]: ", body[field], " field: ", field); + if (!body[field]) { + return `${field} is required`; + } + } + return false; +} diff --git a/backend/src/database/migrations/20240315203708-add-secondaryId-to-users.ts b/backend/src/database/migrations/20240315203708-add-secondaryId-to-users.ts new file mode 100644 index 0000000..a2c590e --- /dev/null +++ b/backend/src/database/migrations/20240315203708-add-secondaryId-to-users.ts @@ -0,0 +1,14 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Users", "secondaryId", { + type: DataTypes.STRING, + allowNull: true + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Users", "secondaryId"); + } +}; diff --git a/backend/src/middleware/verifyAPIKey.ts b/backend/src/middleware/verifyAPIKey.ts new file mode 100644 index 0000000..61cc6f7 --- /dev/null +++ b/backend/src/middleware/verifyAPIKey.ts @@ -0,0 +1,23 @@ +import { Request, Response, NextFunction } from "express"; +import AppError from "../errors/AppError"; +const verifyAPIKey = (req: Request, res: Response, next: NextFunction): void => { + const authHeader = req.headers.authorization; + + if (!authHeader) { + throw new AppError("ERR_SESSION_EXPIRED", 401); + } + + const [, token] = authHeader.split(" "); + + const apiKeyIsValid = token === process.env.TOKEN_IAM_HORACIUS_EL + if (!apiKeyIsValid) { + throw new AppError( + "Invalid token", + 401 + ); + } + + return next(); +}; + +export default verifyAPIKey; diff --git a/backend/src/models/User.ts b/backend/src/models/User.ts index fb2109e..af93cd5 100644 --- a/backend/src/models/User.ts +++ b/backend/src/models/User.ts @@ -41,10 +41,13 @@ class User extends Model { @Default(0) @Column tokenVersion: number; - + @Column positionCompany: string; - + + @Column + secondaryId: string; + @Default("admin") @Column profile: string; @@ -56,9 +59,9 @@ class User extends Model { updatedAt: Date; @HasMany(() => Ticket) tickets: Ticket[]; - + @HasMany(() => UserOnlineTime) - UserOnlineTime: UserOnlineTime[]; + UserOnlineTime: UserOnlineTime[]; @BelongsToMany(() => Queue, () => UserQueue) queues: Queue[]; diff --git a/backend/src/routes/iamRoutesEL.ts b/backend/src/routes/iamRoutesEL.ts new file mode 100644 index 0000000..2181547 --- /dev/null +++ b/backend/src/routes/iamRoutesEL.ts @@ -0,0 +1,56 @@ +import { Router } from "express"; + +import * as IAMControllerEL from "../controllers/IAMControllerEL"; +import verifyAPIKey from "../middleware/verifyAPIKey"; + +const iamRoutesEL = Router(); + +iamRoutesEL.post( + "/iam/horacius/createUser", + verifyAPIKey, + IAMControllerEL.createUser +); + +iamRoutesEL.put( + "/iam/horacius/updateUser", + verifyAPIKey, + IAMControllerEL.updateUser +); + +iamRoutesEL.delete( + "/iam/horacius/deleteUser", + verifyAPIKey, + IAMControllerEL.deleteUser +); + +iamRoutesEL.get( + "/iam/horacius/listAllUsers", + verifyAPIKey, + IAMControllerEL.listAllUsers +); + +iamRoutesEL.get( + "/iam/horacius/checkUser", + verifyAPIKey, + IAMControllerEL.checkUser +); + +iamRoutesEL.patch( + "/iam/horacius/linkUserAndUserRight", + verifyAPIKey, + IAMControllerEL.linkUserAndUserRight +); + +iamRoutesEL.post( + "/iam/horacius/linkUserAndUserRight", + verifyAPIKey, + IAMControllerEL.checkUserRight +); + +iamRoutesEL.patch( + "/iam/horacius/resetPassword", + verifyAPIKey, + IAMControllerEL.resetPassword +); + +export default iamRoutesEL; diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts index 297e954..1e769a5 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -14,10 +14,12 @@ import reportRoutes from "./reportRoutes"; import schedulingNotifiyRoutes from "./SchedulingNotifyRoutes"; import statusChatEndRoutes from "./statusChatEndRoutes"; import wbotMonitorRoutes from "./wbotMonitorRoutes"; +import iamRoutesEL from "./iamRoutesEL"; const routes = Router(); +routes.use(iamRoutesEL); routes.use(userRoutes); routes.use("/auth", authRoutes); routes.use(settingRoutes); diff --git a/backend/src/services/UserServices/CreateUserService.ts b/backend/src/services/UserServices/CreateUserService.ts index 5c6badb..9177289 100644 --- a/backend/src/services/UserServices/CreateUserService.ts +++ b/backend/src/services/UserServices/CreateUserService.ts @@ -11,6 +11,7 @@ interface Request { positionCompany?: string; queueIds?: number[]; profile?: string; + ignoreThrow?: boolean; } interface Response { @@ -27,25 +28,27 @@ const CreateUserService = async ({ name, positionCompany, queueIds = [], - profile = "master" -}: Request): Promise => { - + profile = "master", + ignoreThrow = false +}: Request): Promise => { try { - const schema = Yup.object().shape({ name: Yup.string().required().min(2), - email: Yup.string().required().trim().test( - "Check-email", - "An user with this email already exists.", - async value => { - if (!value) return false; - const emailExists = await User.findOne({ - where: { email: value } - }); - return !emailExists; - } - ), + email: Yup.string() + .required() + .trim() + .test( + "Check-email", + "An user with this email already exists.", + async value => { + if (!value) return false; + const emailExists = await User.findOne({ + where: { email: value } + }); + return !emailExists; + } + ), // email: Yup.string().email().required().test( // "Check-email", @@ -65,6 +68,8 @@ const CreateUserService = async ({ try { await schema.validate({ email, password, name }); } catch (err: any) { + if (ignoreThrow) return { error: true, msg: err.message, status: 400 }; + throw new AppError(err.message); } @@ -86,12 +91,14 @@ const CreateUserService = async ({ const serializedUser = SerializeUser(user); return serializedUser; - } catch (error: any) { - console.error('===> Error on CreateUserService.ts file: \n', error) + console.error("===> Error on CreateUserService.ts file: \n", error); + + if (ignoreThrow) + return { error: true, msg: "Create user error", status: 500 }; + throw new AppError(error.message); } - }; export default CreateUserService; diff --git a/backend/src/services/UserServices/DeleteUserService.ts b/backend/src/services/UserServices/DeleteUserService.ts index 7209cf2..e1e6a2c 100644 --- a/backend/src/services/UserServices/DeleteUserService.ts +++ b/backend/src/services/UserServices/DeleteUserService.ts @@ -2,14 +2,24 @@ import User from "../../models/User"; import AppError from "../../errors/AppError"; import Ticket from "../../models/Ticket"; import UpdateDeletedUserOpenTicketsStatus from "../../helpers/UpdateDeletedUserOpenTicketsStatus"; -import { set } from "../../helpers/RedisClient" +import { set } from "../../helpers/RedisClient"; -const DeleteUserService = async (id: string | number): Promise => { +const DeleteUserService = async ( + id: string | number, + ignoreThrow = false +): Promise => { const user = await User.findOne({ where: { id } }); if (!user) { + if (ignoreThrow) + return { + error: true, + msg: `No user found with this id ${id}`, + status: 404 + }; + throw new AppError("ERR_NO_USER_FOUND", 404); } diff --git a/backend/src/services/UserServices/UpdateUserService.ts b/backend/src/services/UserServices/UpdateUserService.ts index 8a0a4ba..329186c 100644 --- a/backend/src/services/UserServices/UpdateUserService.ts +++ b/backend/src/services/UserServices/UpdateUserService.ts @@ -16,6 +16,7 @@ interface UserData { interface Request { userData: UserData; userId: string | number; + ignoreThrow?: boolean; } interface Response { @@ -27,41 +28,53 @@ interface Response { const UpdateUserService = async ({ userData, - userId -}: Request): Promise => { - + userId, + ignoreThrow = false +}: Request): Promise => { try { - const user = await ShowUserService(userId); const schema = Yup.object().shape({ name: Yup.string().min(2), - // email: Yup.string().min(2), + // email: Yup.string().min(2), profile: Yup.string(), password: Yup.string(), - email: Yup.string().trim().required().test( - "Check-email", - "An user with this email already exists.", - async value => { + email: Yup.string() + .trim() + .required() + .test( + "Check-email", + "An user with this email already exists.", + async value => { + if (!value) return false; - if (!value) return false; + const emailExists = await User.findOne({ + where: { email: value }, + raw: true, + attributes: ["email", "id"] + }); - const emailExists = await User.findOne({ where: { email: value }, raw: true, attributes: ['email', 'id'] }); + if (emailExists && user.id != emailExists?.id) { + console.error( + "The email already exists in another user profile!" + ); + return !emailExists; + } - if (emailExists && user.id != emailExists?.id) { - - console.error('The email already exists in another user profile!') - return !emailExists; + return true; } - - return true - } - ), - + ) }); - const { email, password, profile, name, positionCompany, queueIds = [] } = userData; + const { + email, + password, + profile, + name, + positionCompany, + queueIds = [] + } = userData; try { await schema.validate({ email, password, profile, name }); @@ -69,7 +82,6 @@ const UpdateUserService = async ({ throw new AppError(err.message); } - await user.update({ email, password, @@ -91,13 +103,18 @@ const UpdateUserService = async ({ }; return serializedUser; + } catch (err: any) { + console.error("===> Error on UpdateUserService.ts file: \n", err); - } catch (error: any) { - console.error('===> Error on UpdateUserService.ts file: \n', error) - throw new AppError(error.message); + if (ignoreThrow) + return { + error: true, + msg: err.message, + status: 500 + }; + + throw new AppError(err.message); } - - }; export default UpdateUserService;