diff --git a/src/database/models/user.ts b/src/database/models/user.ts index 3ac0d3c8..1bc5c528 100644 --- a/src/database/models/user.ts +++ b/src/database/models/user.ts @@ -115,6 +115,10 @@ User.init( RoleId: { type: DataTypes.UUID, defaultValue: UUIDV4, + references: { + model: Role, + key: 'id', + }, }, }, { sequelize: sequelize, timestamps: true } diff --git a/src/helpers/token.generator.ts b/src/helpers/token.generator.ts index edd846c2..e2a3cd6a 100644 --- a/src/helpers/token.generator.ts +++ b/src/helpers/token.generator.ts @@ -2,13 +2,12 @@ import * as jwt from 'jsonwebtoken'; import dotenv from 'dotenv'; dotenv.config(); - +export interface UserPayload { + id: string; + email: string; +} // Function to generate token export const userToken = async (userId: string, userEmail: string) => { - interface UserPayload { - id: string; - email: string; - } const payload: UserPayload = { id: userId, email: userEmail, diff --git a/src/middlewares/authMiddlewares.ts b/src/middlewares/authMiddlewares.ts new file mode 100644 index 00000000..ed474de5 --- /dev/null +++ b/src/middlewares/authMiddlewares.ts @@ -0,0 +1,71 @@ +/* eslint-disable @typescript-eslint/no-misused-promises */ +import { Request, Response, NextFunction } from 'express'; +import { config } from 'dotenv'; +import jwt from 'jsonwebtoken'; + +import logger from '../logs/config'; +import User from '../database/models/user'; +import Role from '../database/models/role'; +import { sendInternalErrorResponse } from '../validations'; + +config(); + +export const isAuthenticated = (req: Request, res: Response, next: NextFunction) => { + try { + const authorization = req.headers.authorization; + + if (!authorization) { + logger.error('Authentication required.'); + return res.status(401).json({ message: 'Authentication required.' }); + } + const token = authorization.split(' ')[1]; + jwt.verify(token, process.env.SECRET_KEY!, async (err, decoded: any) => { + if (err) { + if (err.name === 'TokenExpiredError') { + logger.error('Token has expired.'); + return res.status(401).json({ message: 'Token has expired.' }); + } + logger.error('Invalid token.'); + return res.status(401).json({ message: 'Invalid token.' }); + } + + const user = await User.findByPk(decoded.id, { + include: [ + { + model: Role, + as: 'Role', + attributes: ['name'], + }, + ], + }); + + if (!user) { + logger.error('Authorized user not found'); + return res.status(404).json({ message: 'Authorized user not found.' }); + } + // Store the user and decoded token in the request for use in routes + req.user = user; + + return next(); + }); + } catch (error) { + logger.error('Error while checking authentication.', error); + sendInternalErrorResponse(res, error); + } +}; +// Middleware to check user roles +export const checkUserRoles = (requiredRole: string) => { + return async (req: Request, res: Response, next: NextFunction) => { + const user = (await req.user) as any; + const userRole = user.Role.name; + + // Check if the user has all of the required roles + if (requiredRole !== userRole) { + logger.error('Access denied: Your role does not permit this action.'); + return res.status(403).json({ + message: 'Access denied: Your role does not permit this action.', + }); + } + next(); + }; +}; diff --git a/src/routes/roleRoute.ts b/src/routes/roleRoute.ts index c4320ffc..1b9a6a49 100644 --- a/src/routes/roleRoute.ts +++ b/src/routes/roleRoute.ts @@ -1,11 +1,12 @@ +/* eslint-disable @typescript-eslint/no-misused-promises */ import { Router } from 'express'; import { createRole, deleteRole, getAllRoles, getSingleRole, updateRole } from '../controllers/roleControllers'; - +import { isAuthenticated, checkUserRoles } from '../middlewares/authMiddlewares'; const router = Router(); -router.get('/', getAllRoles); +router.get('/', isAuthenticated, getAllRoles); router.get('/:id', getSingleRole); -router.post('/', createRole); +router.post('/', isAuthenticated, checkUserRoles('admin'), createRole); router.patch('/:id', updateRole); router.delete('/:id', deleteRole); diff --git a/src/routes/userRoute.ts b/src/routes/userRoute.ts index e9405d45..b7d61964 100644 --- a/src/routes/userRoute.ts +++ b/src/routes/userRoute.ts @@ -11,16 +11,18 @@ import { userVerify, } from '../controllers/userController'; import multerUpload from '../helpers/multer'; +import { checkUserRoles, isAuthenticated } from '../middlewares/authMiddlewares'; + const router = Router(); router.post('/signup', signupUser); -router.get('/:page?', getAllUser); -router.get('/user/:id', getOneUser); -router.delete('/:id', deleteUser); -router.patch('/edit/:id', multerUpload.single('profileImage'), editUser); // remove id param -router.put('/role/:userId', editUserRole); +router.get('/:page?', isAuthenticated, getAllUser); +router.get('/user/:id', isAuthenticated, getOneUser); +router.delete('/:id', isAuthenticated, checkUserRoles('admin'), deleteUser); +router.patch('/edit/:id', isAuthenticated, multerUpload.single('profileImage'), editUser); // remove id param +router.put('/role/:userId', isAuthenticated, checkUserRoles('admin'), editUserRole); router.get('/:token/verify-email', userVerify); -router.put('/deactivate/:userId', deactivateUserAccount); -router.put('/activate/:userId', activateUserAccount); +router.put('/deactivate/:userId', isAuthenticated, deactivateUserAccount); +router.put('/activate/:userId', isAuthenticated, checkUserRoles('admin'), activateUserAccount); export default router; diff --git a/tsconfig.json b/tsconfig.json index 28fbe1b0..d9f884b5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -94,7 +94,7 @@ // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - "noImplicitReturns": true /* Enable error reporting for codepaths that do not explicitly return in a function. */, + "noImplicitReturns": false /* Enable error reporting for codepaths that do not explicitly return in a function. */, // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */