diff --git a/server/src/controllers/authController.ts b/server/src/controllers/authController.ts index 1b76b249e..23ea6e42e 100755 --- a/server/src/controllers/authController.ts +++ b/server/src/controllers/authController.ts @@ -192,7 +192,8 @@ class AuthController { editUserById = async (req: Request, res: Response, next: NextFunction) => { try { const roles = req?.user?.role; - if (!roles.includes("superadmin")) { + + if (!roles || !roles.includes("superadmin")) { throw new AppError({ message: "Unauthorized", status: 403 }); } @@ -201,7 +202,7 @@ class AuthController { await editUserByIdParamValidation.validateAsync(req.params); // If this is superadmin self edit, allow "superadmin" role - if (userId === req.user._id) { + if (userId === req.user?.id) { await editSuperadminUserByIdBodyValidation.validateAsync(req.body); } else { await editUserByIdBodyValidation.validateAsync(req.body); @@ -217,7 +218,7 @@ class AuthController { editUserPasswordById = async (req: Request, res: Response, next: NextFunction) => { try { const roles = req?.user?.role; - if (!roles.includes("superadmin")) { + if (!roles || !roles.includes("superadmin")) { throw new AppError({ message: "Unauthorized", status: 403 }); } diff --git a/server/src/controllers/incidentController.ts b/server/src/controllers/incidentController.ts index 8a56b5ef8..b17788d80 100644 --- a/server/src/controllers/incidentController.ts +++ b/server/src/controllers/incidentController.ts @@ -69,7 +69,7 @@ class IncidentController { try { const resolvedIncident = await this.incidentService.resolveIncidentManually({ incidentId: req?.params?.incidentId, - userId: req?.user?._id, + userId: req?.user?.id, teamId: req?.user?.teamId, comment: req?.body?.comment, }); diff --git a/server/src/controllers/maintenanceWindowController.ts b/server/src/controllers/maintenanceWindowController.ts index afeba9219..3d212769f 100644 --- a/server/src/controllers/maintenanceWindowController.ts +++ b/server/src/controllers/maintenanceWindowController.ts @@ -46,7 +46,7 @@ class MaintenanceWindowController { try { await getMaintenanceWindowByIdParamValidation.validateAsync(req.params); - const teamId = req.user.teamId; + const teamId = req.user?.teamId; if (!teamId) { throw new AppError({ message: "Team ID is required", status: 400 }); } @@ -130,7 +130,7 @@ class MaintenanceWindowController { await editMaintenanceWindowByIdParamValidation.validateAsync(req.params); await editMaintenanceByIdWindowBodyValidation.validateAsync(req.body); - const teamId = req.user.teamId; + const teamId = req.user?.teamId; if (!teamId) { throw new AppError({ message: "Team ID is required", status: 400 }); } diff --git a/server/src/controllers/monitorController.ts b/server/src/controllers/monitorController.ts index d46845026..0b0184bae 100644 --- a/server/src/controllers/monitorController.ts +++ b/server/src/controllers/monitorController.ts @@ -164,7 +164,7 @@ class MonitorController { try { await createMonitorBodyValidation.validateAsync(req.body); - const userId = requireString(req?.user?._id, "User ID"); + const userId = requireString(req?.user?.id, "User ID"); const teamId = requireTeamId(req?.user?.teamId); const monitor = await this.monitorService.createMonitor(teamId, userId, req.body); @@ -193,7 +193,7 @@ class MonitorController { throw new AppError({ message: "File is empty", status: 400 }); } - const userId = requireString(req?.user?._id, "User ID"); + const userId = requireString(req?.user?.id, "User ID"); const teamId = requireTeamId(req?.user?.teamId); const fileData = req?.file?.buffer?.toString("utf-8"); @@ -286,9 +286,9 @@ class MonitorController { addDemoMonitors = async (req: Request, res: Response, next: NextFunction) => { try { - const _id = requireString(req?.user?._id, "User ID"); + const id = requireString(req?.user?.id, "User ID"); const teamId = requireTeamId(req?.user?.teamId); - const demoMonitors = await this.monitorService.addDemoMonitors({ userId: _id, teamId }); + const demoMonitors = await this.monitorService.addDemoMonitors({ userId: id, teamId }); return res.status(200).json({ success: true, diff --git a/server/src/controllers/notificationController.ts b/server/src/controllers/notificationController.ts index defae2fd3..6f4a7f164 100644 --- a/server/src/controllers/notificationController.ts +++ b/server/src/controllers/notificationController.ts @@ -53,7 +53,7 @@ class NotificationController { throw new AppError({ message: "Team ID is required", status: 400 }); } - const userId = req?.user?._id; + const userId = req?.user?.id; if (!userId) { throw new AppError({ message: "User ID is required", status: 400 }); } diff --git a/server/src/controllers/statusPageController.ts b/server/src/controllers/statusPageController.ts index 105beb230..b63e925c0 100644 --- a/server/src/controllers/statusPageController.ts +++ b/server/src/controllers/statusPageController.ts @@ -21,11 +21,11 @@ class StatusPageController { await createStatusPageBodyValidation.validateAsync(req.body); await imageValidation.validateAsync(req.file); - const { _id, teamId } = req.user; + const { id, teamId } = req.user ?? {}; const statusPage = await this.db.statusPageModule.createStatusPage({ statusPageData: req.body, image: req.file, - userId: _id, + userId: id, teamId, }); return res.status(200).json({ @@ -87,7 +87,7 @@ class StatusPageController { }; getStatusPagesByTeamId = async (req: Request, res: Response, next: NextFunction) => { try { - const teamId = req.user.teamId; + const teamId = req.user?.teamId; const statusPages = await this.db.statusPageModule.getStatusPagesByTeamId(teamId); return res.status(200).json({ diff --git a/server/src/middleware/v1/verifyJWT.js b/server/src/middleware/v1/verifyJWT.ts similarity index 74% rename from server/src/middleware/v1/verifyJWT.js rename to server/src/middleware/v1/verifyJWT.ts index cf983a6c8..b3211d7b6 100755 --- a/server/src/middleware/v1/verifyJWT.js +++ b/server/src/middleware/v1/verifyJWT.ts @@ -1,3 +1,5 @@ +import { NextFunction, Request, Response } from "express"; + import jwt from "jsonwebtoken"; import ServiceRegistry from "../../service/system/serviceRegistry.js"; import SettingsService from "../../service/system/settingsService.js"; @@ -5,20 +7,12 @@ import StringService from "../../service/system/stringService.js"; const SERVICE_NAME = "verifyJWT"; const TOKEN_PREFIX = "Bearer "; -/** - * Verifies the JWT token - * @function - * @param {express.Request} req - * @param {express.Response} res - * @param {express.NextFunction} next - * @returns {express.Response} - */ -const verifyJWT = (req, res, next) => { +const verifyJWT = (req: Request, res: Response, next: NextFunction) => { const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); const token = req.headers["authorization"]; // Make sure a token is provided if (!token) { - const error = new Error(stringService.noAuthToken); + const error: any = new Error(stringService.noAuthToken); error.status = 401; error.service = SERVICE_NAME; next(error); @@ -26,7 +20,7 @@ const verifyJWT = (req, res, next) => { } // Make sure it is properly formatted if (!token.startsWith(TOKEN_PREFIX)) { - const error = new Error(stringService.invalidAuthToken); // Instantiate a new Error object for improperly formatted token + const error: any = new Error(stringService.invalidAuthToken); // Instantiate a new Error object for improperly formatted token error.status = 401; error.service = SERVICE_NAME; error.method = "verifyJWT"; @@ -37,7 +31,7 @@ const verifyJWT = (req, res, next) => { const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); // Verify the token's authenticity const { jwtSecret } = ServiceRegistry.get(SettingsService.SERVICE_NAME).getSettings(); - jwt.verify(parsedToken, jwtSecret, (err, decoded) => { + jwt.verify(parsedToken, jwtSecret, (err: any, decoded: any) => { if (err) { const errorMessage = err.name === "TokenExpiredError" ? stringService.expiredAuthToken : stringService.invalidAuthToken; err.details = { msg: errorMessage }; diff --git a/server/src/repositories/users/IUsersRepository.ts b/server/src/repositories/users/IUsersRepository.ts index 9678de47a..3fa094c54 100644 --- a/server/src/repositories/users/IUsersRepository.ts +++ b/server/src/repositories/users/IUsersRepository.ts @@ -4,9 +4,12 @@ export interface IUsersRepository { create(user: Partial, imageFile?: Express.Multer.File | null): Promise; // fetch findByEmail(email: string): Promise; + findById(id: string): Promise; + findAll(): Promise; // update updateById(id: string, patch: Partial, file?: Express.Multer.File | null): Promise; // delete + deleteById(id: string): Promise; // other findSuperAdmin(): Promise; } diff --git a/server/src/repositories/users/MongoUsersRepository.ts b/server/src/repositories/users/MongoUsersRepository.ts index b2687ff4a..023e8aefb 100644 --- a/server/src/repositories/users/MongoUsersRepository.ts +++ b/server/src/repositories/users/MongoUsersRepository.ts @@ -82,6 +82,20 @@ class MongoUsersRepository implements IUsersRepository { return this.toEntity(user); }; + findById = async (id: string) => { + const user = await UserModel.findById(id).select("-password").select("-profileImage"); + if (!user) { + throw new Error("User not found"); + } + + return this.toEntity(user); + }; + + findAll = async () => { + const users = await UserModel.find().select("-password").select("-profileImage"); + return this.mapDocuments(users); + }; + updateById = async (id: string, patch: Partial, file?: Express.Multer.File | null): Promise => { const candidateUser = { ...patch }; @@ -113,6 +127,14 @@ class MongoUsersRepository implements IUsersRepository { return this.toEntity(updatedUser); }; + deleteById = async (id: string) => { + const deletedUser = await UserModel.findByIdAndDelete(id); + if (!deletedUser) { + throw new AppError({ message: "User not found", service: SERVICE_NAME, status: 404 }); + } + return this.toEntity(deletedUser); + }; + findSuperAdmin = async () => { const superAdmin = await UserModel.findOne({ role: "superadmin" }); if (superAdmin !== null) { @@ -120,6 +142,13 @@ class MongoUsersRepository implements IUsersRepository { } return false; }; + + private mapDocuments = (documents: UserDocument[]): User[] => { + if (!documents?.length) { + return []; + } + return documents.map((doc) => this.toEntity(doc)); + }; } export default MongoUsersRepository; diff --git a/server/src/service/business/userService.ts b/server/src/service/business/userService.ts index efd599299..7d9e5811e 100644 --- a/server/src/service/business/userService.ts +++ b/server/src/service/business/userService.ts @@ -187,7 +187,7 @@ class UserService { }; requestRecovery = async (email: string) => { - const user = await this.db.userModule.getUserByEmail(email); + const user = await this.usersRepository.findByEmail(email); // Delete existing tokens await this.recoveryTokensRepository.deleteManyByEmail(email); @@ -231,14 +231,14 @@ class UserService { return { user: existingUser, token }; }; - deleteUser = async (user: any) => { + deleteUser = async (user: User) => { const email = user?.email; if (!email) { throw this.errorService.createBadRequestError("No email in request"); } const teamId = user?.teamId; - const userId = user?._id; + const userId = user?.id; if (!teamId) { throw this.errorService.createBadRequestError("No team ID in request"); @@ -267,24 +267,28 @@ class UserService { )); } // 6. Delete the user by id - await this.db.userModule.deleteUser(userId); + await this.usersRepository.deleteById(userId); }; getAllUsers = async () => { - const users = await this.db.userModule.getAllUsers(); - return users; + return await this.usersRepository.findAll(); }; getUserById = async (roles: any, userId: any) => { - const user = await this.db.userModule.getUserById(roles, userId); + if (!roles.includes("superadmin")) { + throw new AppError({ message: "User is not a superadmin", service: SERVICE_NAME, status: 403 }); + } + const user = await this.usersRepository.findById(userId); + return user; }; - editUserById = async (userId: any, user: any) => { - await this.db.userModule.editUserById(userId, user); + editUserById = async (userId: any, patch: Partial) => { + await this.usersRepository.updateById(userId, patch, null); }; + setPasswordByUserId = async (userId: any, password: string) => { - const updatedUser = await this.db.userModule.updateUser({ userId: userId, user: { password: password }, file: null }); + const updatedUser = await this.usersRepository.updateById(userId, { password }, null); return updatedUser; }; } diff --git a/server/src/types/express.d.ts b/server/src/types/express.d.ts index b9f7386f6..66755659c 100644 --- a/server/src/types/express.d.ts +++ b/server/src/types/express.d.ts @@ -1,10 +1,10 @@ -import { ITokenizedUser } from "../db/models/index.ts"; +import type { User } from "@/types/index.js"; declare global { namespace Express { interface Request { file?: Multer.File; - user?: ITokenizedUser; + user?: User | undefined; resource?: any; } }