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

Password reset #22

Merged
merged 2 commits 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,168 changes: 676 additions & 4,492 deletions package-lock.json

Large diffs are not rendered by default.

15 changes: 14 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,26 @@
"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",
"crypto": "^1.0.1",
"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",
"nodemailer": "^6.9.16",
"pg": "^8.13.1",
"prisma": "^6.1.0"
},
"devDependencies": {
"jest": "^29.7.0",
Expand Down
15 changes: 8 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,27 +47,28 @@ 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 {
user_id Int @id @default(autoincrement())
username String? @unique @db.VarChar(50)
email String? @unique @db.VarChar(100)
password_hash String? @db.VarChar(255)
resetPasswordToken String? // Nullable field
resetPasswordExpires DateTime? // Nullable field
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;
86 changes: 86 additions & 0 deletions src/controllers/authController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
const crypto = require('crypto');
const bcrypt = require('bcryptjs');
const { PrismaClient } = require('@prisma/client');
const sendEmail = require('../utils/emailService');

const prisma = new PrismaClient();

exports.forgotPassword = async (req, res) => {
try {
const { email } = req.body;

// Check if the user exists
const user = await prisma.users.findUnique({ where: { email } });
if (!user) {
return res.status(404).json({ message: 'User not found' });
}

// Generate reset token
const resetToken = crypto.randomBytes(20).toString('hex');
const hashedToken = crypto.createHash('sha256').update(resetToken).digest('hex');
const tokenExpiry = new Date(Date.now() + 5 * 60 * 1000); // 5 minutes

// Update user with token and expiry
await prisma.users.update({
where: { email },
data: {
resetPasswordToken: hashedToken,
resetPasswordExpires: tokenExpiry,
},
});

// Send reset email
const resetURL = `${req.protocol}://${req.get('host')}/api/v1/reset-password/${resetToken}`;
const message = `
<h2>Password Reset</h2>
<p>Click the link below to reset your password:</p>
<a href="${resetURL}">${resetURL}</a>
`;

await sendEmail(user.email, 'Password Reset Request', message);

res.status(200).json({ message: 'Password reset email sent' });
} catch (err) {
console.error(err);
res.status(500).json({ message: 'Internal server error' });
}
};

exports.resetPassword = async (req, res) => {

try {
const { token } = req.params;
const { password } = req.body;

const hashedToken = crypto.createHash('sha256').update(token).digest('hex');

// Find the user with the token and check expiry
const user = await prisma.users.findFirst({
where: {
resetPasswordToken: hashedToken,
resetPasswordExpires: { gt: new Date() },
},
});

if (!user) {
return res.status(400).json({ message: 'Invalid or expired token' });
}

// Update password and clear token
const hashedPassword = await bcrypt.hash(password, 10);

await prisma.users.update({
where: { user_id: user.user_id },
data: {
password_hash: hashedPassword,
resetPasswordToken: null,
resetPasswordExpires: null,
},
});

res.status(200).json({ message: 'Password reset successful' });
} catch (err) {
console.error(err);
res.status(500).json({ message: 'Internal server error' });
}
};
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
Loading
Loading