Skip to content

Commit d344fd7

Browse files
authored
Merge pull request #22 from Code-the-Dream-School/passwordReset
Password reset
2 parents 2b570f8 + 0e9d5f6 commit d344fd7

19 files changed

+1222
-4542
lines changed

package-lock.json

+676-4,492
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+14-1
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,26 @@
55
"main": "app.js",
66
"scripts": {
77
"dev": "nodemon src/server.js",
8+
"seed": "node prisma/seed.js",
89
"test": "jest"
910
},
1011
"author": "",
1112
"license": "ISC",
1213
"dependencies": {
1314
"@prisma/client": "^6.1.0",
14-
"cors": "^2.8.5"
15+
"bcryptjs": "^2.4.3",
16+
"cors": "^2.8.5",
17+
"crypto": "^1.0.1",
18+
"dotenv": "^16.4.7",
19+
"express": "^4.18.2",
20+
"express-favicon": "^2.0.4",
21+
"joi": "^17.13.3",
22+
"jsonwebtoken": "^9.0.2",
23+
"mongoose": "^7.0.1",
24+
"morgan": "^1.10.0",
25+
"nodemailer": "^6.9.16",
26+
"pg": "^8.13.1",
27+
"prisma": "^6.1.0"
1528
},
1629
"devDependencies": {
1730
"jest": "^29.7.0",

prisma/schema.prisma

+8-7
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ model projects {
3838
is_public Boolean?
3939
created_by Int?
4040
created_at DateTime? @default(now()) @db.Timestamp(6)
41-
tags String[] @default([])
41+
tags String[] @default([])
4242
comments comments[]
4343
likes likes[]
4444
users users? @relation(fields: [created_by], references: [user_id], onUpdate: NoAction)
@@ -47,27 +47,28 @@ model projects {
4747
model roles {
4848
role_id Int @id @default(autoincrement())
4949
role_name String? @db.VarChar(50)
50-
user_role user_role[] @ignore
50+
user_role user_role[]
5151
}
5252

5353
/// The underlying table does not contain a valid unique identifier and can therefore currently not be handled by Prisma Client.
5454
model user_role {
5555
user_id Int
5656
role_id Int
57-
roles roles? @relation(fields: [role_id], references: [role_id], onDelete: Cascade, onUpdate: NoAction)
58-
users users? @relation(fields: [user_id], references: [user_id], onDelete: Cascade, onUpdate: NoAction)
59-
60-
@@id([user_id, role_id]) // Composite primary key (if applicable)
57+
roles roles @relation(fields: [role_id], references: [role_id], onDelete: Cascade, onUpdate: NoAction)
58+
users users @relation(fields: [user_id], references: [user_id], onDelete: Cascade, onUpdate: NoAction)
6159
60+
@@id([user_id, role_id])
6261
}
6362

6463
model users {
6564
user_id Int @id @default(autoincrement())
6665
username String? @unique @db.VarChar(50)
6766
email String? @unique @db.VarChar(100)
6867
password_hash String? @db.VarChar(255)
68+
resetPasswordToken String? // Nullable field
69+
resetPasswordExpires DateTime? // Nullable field
6970
comments comments[]
7071
likes likes[]
7172
projects projects[]
72-
user_role user_role[] @ignore
73+
user_role user_role[]
7374
}

src/app.js

+13-5
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,29 @@ const cors = require('cors')
44
const favicon = require('express-favicon');
55
const logger = require('morgan');
66

7-
const mainRouter = require('./routes/mainRouter.js');
8-
const userRouter = require('./routes/userRouter.js');
7+
8+
const mainRouter = require('./routes/mainRouter');
9+
const authRouter = require('./routes/authRouter');
10+
const userRouter = require('./routes/userRouter');
911
const projectRouter = require('./routes/projectRouter.js');
1012

13+
1114
// middleware
1215
app.use(cors());
1316
app.use(express.json());
1417
app.use(express.urlencoded({ extended: false }));
1518
app.use(logger('dev'));
16-
app.use(express.static('public'))
19+
app.use(express.static('public'));
1720
app.use(favicon(__dirname + '/public/favicon.ico'));
1821

22+
const authenticateToken = require('./middlewares/authMiddleware');
23+
1924
// routes
20-
app.use('/api/v1', mainRouter);
25+
26+
app.use('/api/v1', authRouter);
2127
app.use('/api/v1', userRouter);
22-
app.use('/api/v1', projectRouter);
28+
app.use('/api/v1', authenticateToken, mainRouter);
29+
app.use('/api/v1', authenticateToken, projectRouter);
30+
2331

2432
module.exports = app;

src/controllers/authController.js

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
const crypto = require('crypto');
2+
const bcrypt = require('bcryptjs');
3+
const { PrismaClient } = require('@prisma/client');
4+
const sendEmail = require('../utils/emailService');
5+
6+
const prisma = new PrismaClient();
7+
8+
exports.forgotPassword = async (req, res) => {
9+
try {
10+
const { email } = req.body;
11+
12+
// Check if the user exists
13+
const user = await prisma.users.findUnique({ where: { email } });
14+
if (!user) {
15+
return res.status(404).json({ message: 'User not found' });
16+
}
17+
18+
// Generate reset token
19+
const resetToken = crypto.randomBytes(20).toString('hex');
20+
const hashedToken = crypto.createHash('sha256').update(resetToken).digest('hex');
21+
const tokenExpiry = new Date(Date.now() + 5 * 60 * 1000); // 5 minutes
22+
23+
// Update user with token and expiry
24+
await prisma.users.update({
25+
where: { email },
26+
data: {
27+
resetPasswordToken: hashedToken,
28+
resetPasswordExpires: tokenExpiry,
29+
},
30+
});
31+
32+
// Send reset email
33+
const resetURL = `${req.protocol}://${req.get('host')}/api/v1/reset-password/${resetToken}`;
34+
const message = `
35+
<h2>Password Reset</h2>
36+
<p>Click the link below to reset your password:</p>
37+
<a href="${resetURL}">${resetURL}</a>
38+
`;
39+
40+
await sendEmail(user.email, 'Password Reset Request', message);
41+
42+
res.status(200).json({ message: 'Password reset email sent' });
43+
} catch (err) {
44+
console.error(err);
45+
res.status(500).json({ message: 'Internal server error' });
46+
}
47+
};
48+
49+
exports.resetPassword = async (req, res) => {
50+
51+
try {
52+
const { token } = req.params;
53+
const { password } = req.body;
54+
55+
const hashedToken = crypto.createHash('sha256').update(token).digest('hex');
56+
57+
// Find the user with the token and check expiry
58+
const user = await prisma.users.findFirst({
59+
where: {
60+
resetPasswordToken: hashedToken,
61+
resetPasswordExpires: { gt: new Date() },
62+
},
63+
});
64+
65+
if (!user) {
66+
return res.status(400).json({ message: 'Invalid or expired token' });
67+
}
68+
69+
// Update password and clear token
70+
const hashedPassword = await bcrypt.hash(password, 10);
71+
72+
await prisma.users.update({
73+
where: { user_id: user.user_id },
74+
data: {
75+
password_hash: hashedPassword,
76+
resetPasswordToken: null,
77+
resetPasswordExpires: null,
78+
},
79+
});
80+
81+
res.status(200).json({ message: 'Password reset successful' });
82+
} catch (err) {
83+
console.error(err);
84+
res.status(500).json({ message: 'Internal server error' });
85+
}
86+
};

src/controllers/projectController.js

+31-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const projectModel = require("../models/projectModel");
2+
const {getLoggedInUserId, checkProjectOwner, checkUserRoleAdmin} = require('../services/userService');
23

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

2526

2627
addProject: async (req, res) => {
28+
29+
const user_id = await getLoggedInUserId(req);
2730
try {
28-
const { user_id } = req.params;
2931
const {
3032
name,
3133
description,
@@ -75,7 +77,8 @@ const projectController = {
7577

7678
updateProject: async (req, res) => {
7779
try {
78-
const { user_id, project_id } = req.params;
80+
const { project_id } = req.params;
81+
const user_id = await getLoggedInUserId (req);
7982
const {
8083
name,
8184
description,
@@ -162,9 +165,10 @@ const projectController = {
162165

163166

164167
addComment: async (req, res) => {
168+
const user_id = await getLoggedInUserId(req);
165169
try {
166170
const { project_id } = req.params;
167-
const { comment_content, user_id } = req.body;
171+
const { comment_content } = req.body;
168172

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

228232

229233
deleteProject: async (req, res) => {
234+
const user_id = await getLoggedInUserId(req);
235+
const checkIfUserAdmin = await checkUserRoleAdmin(req);
236+
const checkIfUserProjectOwner = await checkProjectOwner(req);
230237
try {
231-
const { user_id, project_id } = req.params;
238+
const { project_id } = req.params;
232239

233240
if (!user_id || !project_id) {
234241
return res.status(400).json({
235242
success: false,
236-
message: "Both user_id and project_id are required.",
243+
message: " project_id are required.",
237244
});
238245
}
246+
if(checkIfUserAdmin || checkIfUserProjectOwner){
247+
const result = await projectModel.deleteProject(
248+
parseInt(user_id, 10),
249+
parseInt(project_id, 10)
250+
);
251+
}else {
252+
return res.status(405).json({
253+
success: false,
254+
message: "You are not project owner, please contact admin to delete the project",
255+
});
256+
}
257+
239258

240-
const result = await projectModel.deleteProject(
241-
parseInt(user_id, 10),
242-
parseInt(project_id, 10)
243-
);
244-
245-
res.status(200).json(result);
259+
return res.status(200).json(result);
246260
} catch (error) {
247-
res.status(500).json({
261+
return res.status(500).json({
248262
success: false,
249263
message: error.message,
250264
});
@@ -255,8 +269,10 @@ const projectController = {
255269

256270

257271
addLike: async (req, res) => {
272+
const user_id = await getLoggedInUserId(req);
273+
console.log(user_id)
258274
try {
259-
const { user_id, project_id } = req.params;
275+
const { project_id } = req.params;
260276
if (!user_id || !project_id) {
261277
return res.status(400).json({
262278
success: false,
@@ -279,8 +295,9 @@ const projectController = {
279295
},
280296

281297
removeLike: async (req, res) => {
298+
const user_id = await getLoggedInUserId(req);
282299
try {
283-
const { user_id, project_id } = req.params;
300+
const { project_id } = req.params;
284301
if (!user_id || !project_id) {
285302
return res.status(400).json({
286303
success: false,

0 commit comments

Comments
 (0)