Skip to content
Open
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
3 changes: 3 additions & 0 deletions lib/prisma.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { PrismaClient } from "@prisma/client";


// const prisma = new PrismaClient()

// Create a global variable to store the Prisma client instance
// This prevents multiple instances during development with hot reload
const globalForPrisma = globalThis;
Expand Down
40 changes: 39 additions & 1 deletion middleware/auth.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import jwt from "jsonwebtoken";
import prisma from "../lib/prisma.js";
import { PrismaClient } from "@prisma/client";

//const prisma = new PrismaClient();

const JWT_SECRET = process.env.JWT_SECRET || "your-secret-key";

Expand All @@ -8,12 +11,47 @@ export const authenticateToken = async (req, res, next) => {
// TODO: Implement the authentication middleware
// 1. Get the token from the request header
// 2. Verify the token

const token = req.headers["authorization"]?.split(" ")[1];

if (!token) {
return res.status(401).json({
success: false,
message: "Access token required",
});
}

const decoded = jwt.verify(
token,
process.env.JWT_SECRET || "your-secret-key"
);

// 3. Get the user from the database
// 4. If the user doesn't exist, throw an error
// 5. Attach the user to the request object
// 6. Call the next middleware


const user = await prisma.user.findUnique({
where: { id: decoded.userId },
select: {
id: true,
name: true,
email: true,
role: true

},
});

if (!user) {
return res.status(401).json({
success: false,
message: "Invalid token - user not found",
});
}
req.user = user
req.role = user.role
next()


} catch (error) {
if (error.name === "JsonWebTokenError") {
Expand Down
19 changes: 19 additions & 0 deletions middleware/role.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const requireRole = (...allowedRoles) => {
return(req, res, next) => {
if (!req.user || !req.role) {
return res.status(401).json({
success: false,
message: "Authentication required",
});
}

if (!allowedRoles.includes(req.role)) {
return res.status(403).json({
success: false,
message: `Access denied.`// Requered role: ${allowedRoles.join(" or ")}`,
});

}
next()
}
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@
"license": "MIT",
"dependencies": {
"@prisma/client": "^5.7.1",
"bcrypt": "^6.0.0",
"bcryptjs": "^2.4.3",
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.2"
"jsonwebtoken": "^9.0.3"
},
"devDependencies": {
"nodemon": "^3.0.1",
Expand Down
6 changes: 6 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@ model User {
email String @unique
password String
name String
role Role @default(USER)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tasks Task[]

@@map("users")
}

enum Role {
USER
ADMIN
}

model Task {
id String @id @default(cuid())
title String
Expand Down
117 changes: 112 additions & 5 deletions routes/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,83 @@ import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
import prisma from "../lib/prisma.js";
import { authenticateToken } from "../middleware/auth.js";

import { requireRole } from "../middleware/role.js";
const router = express.Router();
//const prisma = new PrismaClient();
const JWT_SECRET = process.env.JWT_SECRET || "your-secret-key";

// POST /api/auth/register - Register a new user
router.post("/register", async (req, res) => {
try {
// TODO: Implement the registration logic

const { name, email, password, role } = req.body;
// 1. Validate the input

if (!name || !email || !password ||!role) {
return res.status(400).json({
success: false,
message: "name, email, role, and password are required",
});
}

// Validate role if provided
if (role && !["USER", "ADMIN"].includes(role)) {
return res.status(400).json({
success: false,
message: "role must be either USER or ADMIN",
});
}
// 2. Check if the user already exists

const existingUser = await prisma.user.findUnique({
where: { email: email },
});

if (existingUser) {
return res.status(400).json({
success: false,
message: "User with this email already exists",
});
}
// 3. Hash the password

const saltRounds = 10;
const hashedPassword = await bcrypt.hash(password, saltRounds);
// 4. Create the user

const newUser = await prisma.user.create({
data: {
name,
email,
password: hashedPassword,
role: role || "USER",
},
select: {
id: true,
name: true,
email: true,
role: true,
},
});

// 5. Generate a JWT token
// 6. Return the user data and token

const token = jwt.sign(
{ userId: newUser.id, role: newUser.role },
process.env.JWT_SECRET || "your-secret-key",
{ expiresIn: "24h" }
);
// 6. Return the user data and token

res.status(201).json({
success: true,
message: "User registered successfully",
data: {
user: newUser,
token,
},
});

} catch (error) {
console.error("Registration error:", error);
Expand All @@ -31,15 +92,61 @@ router.post("/register", async (req, res) => {
});

// POST /api/auth/login - Login user
router.post("/login", async (req, res) => {
router.post("/login", async (req, res) => {
try {
// TODO: Implement the login logic

const { email, password } = req.body;
// 1. Validate the input

if (!email || !password) {
return res.status(400).json({
success: false,
message: "Email and password are required",
});
}
// 2. Check if the user exists

const user = await prisma.user.findUnique({
where: { email: email },
});

if (!user) {
return res.status(401).json({
success: false,
message: "Invalid email or password",
});
}
// 3. Compare the password

const isPasswordValid = await bcrypt.compare(password, user.password);

if (!isPasswordValid) {
return res.status(401).json({
success: false,
message: "Invalid email or password",
});
}

// 4. Generate a JWT token

const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET || "secretkey",
{ expiresIn: "24h" }
);
// 5. Return the user data and token


const { password: _, ...userData } = user;

res.json({
success: true,
message: "Login successful",
data: {
user: userData,
token,
},
});

} catch (error) {
console.error("Login error:", error);
Expand All @@ -52,7 +159,7 @@ router.post("/login", async (req, res) => {
});

// GET /api/auth/me - Get current user profile (protected route)
router.get("/me", authenticateToken, async (req, res) => {
router.get("/me", authenticateToken, requireRole("USER"), async (req, res) => {
try {
// req.user will be set by the authenticateToken middleware
const { password, ...userWithoutPassword } = req.user;
Expand Down
5 changes: 3 additions & 2 deletions routes/tasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
getSubtasksByTaskId,
} from "../services/taskServices.js";
import { authenticateToken } from "../middleware/auth.js";
import { requireRole } from "../middleware/role.js";

const router = express.Router();

Expand All @@ -22,7 +23,7 @@ router.use(authenticateToken);
// This route handles GET requests to /api/tasks
// req = request object (contains data sent by client)
// res = response object (used to send data back to client)
router.get("/tasks", async (req, res) => {
router.get("/tasks", authenticateToken, requireRole("ADMIN"), async (req, res) => {
try {
const tasks = await getAllTasks(req.user.id);

Expand All @@ -42,7 +43,7 @@ router.get("/tasks", async (req, res) => {
// GET /api/tasks/:id - Get task by ID for the authenticated user
// :id is a route parameter - it captures the value from the URL
// Example: /api/tasks/1 will set req.params.id = "1"
router.get("/tasks/:id", async (req, res) => {
router.get("/tasks/:id", authenticateToken, requireRole("ADMIN"), async (req, res) => {
try {
const { id } = req.params; // Extract the ID from the URL
const task = await getTaskById(id, req.user.id);
Expand Down