Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug and security fix #21

Merged
1 commit merged into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5,154 changes: 661 additions & 4,493 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 12 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,24 @@
"main": "app.js",
"scripts": {
"dev": "nodemon src/server.js",
"seed": "node prisma/seed.js",
"test": "jest"
},
"author": "",
"license": "ISC",
"dependencies": {
"@prisma/client": "^6.1.0",
"cors": "^2.8.5"
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.18.2",
"express-favicon": "^2.0.4",
"joi": "^17.13.3",
"jsonwebtoken": "^9.0.2",
"mongoose": "^7.0.1",
"morgan": "^1.10.0",
"pg": "^8.13.1",
"prisma": "^6.1.0"
},
"devDependencies": {
"jest": "^29.7.0",
Expand Down
13 changes: 6 additions & 7 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ model projects {
is_public Boolean?
created_by Int?
created_at DateTime? @default(now()) @db.Timestamp(6)
tags String[] @default([])
tags String[] @default([])
comments comments[]
likes likes[]
users users? @relation(fields: [created_by], references: [user_id], onUpdate: NoAction)
Expand All @@ -47,18 +47,17 @@ model projects {
model roles {
role_id Int @id @default(autoincrement())
role_name String? @db.VarChar(50)
user_role user_role[] @ignore
user_role user_role[]
}

/// The underlying table does not contain a valid unique identifier and can therefore currently not be handled by Prisma Client.
model user_role {
user_id Int
role_id Int
roles roles? @relation(fields: [role_id], references: [role_id], onDelete: Cascade, onUpdate: NoAction)
users users? @relation(fields: [user_id], references: [user_id], onDelete: Cascade, onUpdate: NoAction)

@@id([user_id, role_id]) // Composite primary key (if applicable)
roles roles @relation(fields: [role_id], references: [role_id], onDelete: Cascade, onUpdate: NoAction)
users users @relation(fields: [user_id], references: [user_id], onDelete: Cascade, onUpdate: NoAction)

@@id([user_id, role_id])
}

model users {
Expand All @@ -69,5 +68,5 @@ model users {
comments comments[]
likes likes[]
projects projects[]
user_role user_role[] @ignore
user_role user_role[]
}
18 changes: 13 additions & 5 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,29 @@ const cors = require('cors')
const favicon = require('express-favicon');
const logger = require('morgan');

const mainRouter = require('./routes/mainRouter.js');
const userRouter = require('./routes/userRouter.js');

const mainRouter = require('./routes/mainRouter');
const authRouter = require('./routes/authRouter');
const userRouter = require('./routes/userRouter');
const projectRouter = require('./routes/projectRouter.js');


// middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(logger('dev'));
app.use(express.static('public'))
app.use(express.static('public'));
app.use(favicon(__dirname + '/public/favicon.ico'));

const authenticateToken = require('./middlewares/authMiddleware');

// routes
app.use('/api/v1', mainRouter);

app.use('/api/v1', authRouter);
app.use('/api/v1', userRouter);
app.use('/api/v1', projectRouter);
app.use('/api/v1', authenticateToken, mainRouter);
app.use('/api/v1', authenticateToken, projectRouter);


module.exports = app;
45 changes: 31 additions & 14 deletions src/controllers/projectController.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const projectModel = require("../models/projectModel");
const {getLoggedInUserId, checkProjectOwner, checkUserRoleAdmin} = require('../services/userService');

const projectController = {
getAllProjects: async (req, res) => {
Expand All @@ -24,8 +25,9 @@ const projectController = {


addProject: async (req, res) => {

const user_id = await getLoggedInUserId(req);
try {
const { user_id } = req.params;
const {
name,
description,
Expand Down Expand Up @@ -75,7 +77,8 @@ const projectController = {

updateProject: async (req, res) => {
try {
const { user_id, project_id } = req.params;
const { project_id } = req.params;
const user_id = await getLoggedInUserId (req);
const {
name,
description,
Expand Down Expand Up @@ -162,9 +165,10 @@ const projectController = {


addComment: async (req, res) => {
const user_id = await getLoggedInUserId(req);
try {
const { project_id } = req.params;
const { comment_content, user_id } = req.body;
const { comment_content } = req.body;

if (!project_id || !comment_content || !user_id) {
return res.status(400).json({
Expand Down Expand Up @@ -227,24 +231,34 @@ const projectController = {


deleteProject: async (req, res) => {
const user_id = await getLoggedInUserId(req);
const checkIfUserAdmin = await checkUserRoleAdmin(req);
const checkIfUserProjectOwner = await checkProjectOwner(req);
try {
const { user_id, project_id } = req.params;
const { project_id } = req.params;

if (!user_id || !project_id) {
return res.status(400).json({
success: false,
message: "Both user_id and project_id are required.",
message: " project_id are required.",
});
}
if(checkIfUserAdmin || checkIfUserProjectOwner){
const result = await projectModel.deleteProject(
parseInt(user_id, 10),
parseInt(project_id, 10)
);
}else {
return res.status(405).json({
success: false,
message: "You are not project owner, please contact admin to delete the project",
});
}


const result = await projectModel.deleteProject(
parseInt(user_id, 10),
parseInt(project_id, 10)
);

res.status(200).json(result);
return res.status(200).json(result);
} catch (error) {
res.status(500).json({
return res.status(500).json({
success: false,
message: error.message,
});
Expand All @@ -255,8 +269,10 @@ const projectController = {


addLike: async (req, res) => {
const user_id = await getLoggedInUserId(req);
console.log(user_id)
try {
const { user_id, project_id } = req.params;
const { project_id } = req.params;
if (!user_id || !project_id) {
return res.status(400).json({
success: false,
Expand All @@ -279,8 +295,9 @@ const projectController = {
},

removeLike: async (req, res) => {
const user_id = await getLoggedInUserId(req);
try {
const { user_id, project_id } = req.params;
const { project_id } = req.params;
if (!user_id || !project_id) {
return res.status(400).json({
success: false,
Expand Down
64 changes: 50 additions & 14 deletions src/controllers/userController.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,36 @@
const { createUser } = require('../services/userService');
const userModel = require('../models/userModel');
const {getLoggedInUserId, checkUserRoleAdmin} = require('../services/userService');
const { exist } = require('joi');

const userController = {
getAllUsers: async (req, res) => {

createUserController: async (req, res) => {
try {
const users = await userModel.getAllUsers();
res.status(200).json({ success: true, data: users });
const user = await createUser(req.body);
res.status(201).json(user);
} catch (error) {
res.status(500).json({ success: false, message: error.message });
console.log(error)
res.status(400).json({ error: error.message });
}
},

getAllUsers: async (req, res) => {

const checkIfAdmin = await checkUserRoleAdmin(req); // checking for user role if admin will return true.

if(checkIfAdmin) { // if the user is with admin will return see the user list
try {
const users = await userModel.getAllUsers();
return res.status(200).json({ success: true, data: users });
} catch (error) {
return res.status(500).json({ success: false, message: error.message });
}
}else {

return res.status(500).json({ success: false, message: "Only admin can view the user list" });
}

},

getUserInfoById: async (req, res) => {
Expand All @@ -21,6 +44,7 @@ const userController = {
},

addUser: async (req, res) => {

try {
const { username, email, password_hash } = req.body;
if (!username || !email || !password_hash) {
Expand Down Expand Up @@ -107,14 +131,20 @@ const userController = {
data: updatedRole,
});
} catch (error) {
res.status(500).json({
return res.status(500).json({
success: false,
message: error.message,
});
}
},

deleteUser: async (req, res) => {

const checkIfAdmin = await checkUserRoleAdmin(req); // checking for user role if admin will return true.
const checkLoggedInUser = await getLoggedInUserId(req);

// console.log(checkIfAdmin, checkLoggedInUser)

try {
const { user_id } = req.params;

Expand All @@ -133,20 +163,26 @@ const userController = {
message: "Invalid user_id. It must be an integer.",
});
}

await userModel.deleteUser(userId);

res.status(200).json({
success: true,
message: "User deleted successfully.",
});


if(checkIfAdmin || checkLoggedInUser == userId) {
await userModel.deleteUser(userId);
return res.status(200).json({
success: true,
message: "User deleted successfully.",
});

} else {
return res.status(403).json({ success: false, message: "Please contact admin to delete the user. " });
}

} catch (error) {
res.status(500).json({
return res.status(500).json({
success: false,
message: error.message,
});
}
},
};

module.exports = userController;
module.exports = userController;
46 changes: 46 additions & 0 deletions src/middlewares/authMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const jwt = require('jsonwebtoken');
require('dotenv').config();

const JWT_SECRET = process.env.JWT_SECRET;

const authenticateJWT = async (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ message: 'Authorization token is required' });
}

try {
const decoded = jwt.verify(token, SECRET_KEY);

// Check if token exists in the Session table
const session = await prisma.session.findUnique({
where: { token },
});

if (!session || new Date() > session.expiresAt) {
return res.status(401).json({ message: 'Session expired or invalid' });
}

req.user = decoded; // Attach user info to request
next();
} catch (error) {
return res.status(403).json({ message: 'Invalid or expired token' });
}
};

const authenticateToken = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access denied, token missing' });
}

try {
const decoded = jwt.verify(token, JWT_SECRET);
req.user = decoded; // Attach user info to request object
next();
} catch (error) {
res.status(403).json({ error: 'Invalid or expired token' });
}
};

module.exports = authenticateToken;
33 changes: 33 additions & 0 deletions src/middlewares/prismaValidationMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const userSchema = require('../models/userSchema');
const bcrypt = require('bcryptjs');

const prismaValidationMiddleware = async (params, next) => {

if (params.model === 'users' && params.action === 'create') {

const userInput = params.args.data;
// Validate input data
const { error } = userSchema.validate(userInput);
if (error) {
throw new Error(`Validation Error: ${error.message}`);
}
}

// Intercept User model actions
if (params.model === 'users') {

if (params.action === 'create' || params.action === 'update') {
const userInput = params.args.data;

if (userInput && userInput.password_hash) {
// Hash the password
const salt = await bcrypt.genSalt(10);
userInput.password_hash = await bcrypt.hash(userInput.password_hash, salt);
}
}
}

return next(params);
};

module.exports = prismaValidationMiddleware;
Loading
Loading