diff --git a/lib/prisma.js b/lib/prisma.js index 569e176..a2fb942 100644 --- a/lib/prisma.js +++ b/lib/prisma.js @@ -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; diff --git a/middleware/auth.js b/middleware/auth.js index 7deb650..7cc8c0f 100644 --- a/middleware/auth.js +++ b/middleware/auth.js @@ -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"; @@ -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") { diff --git a/middleware/role.js b/middleware/role.js new file mode 100644 index 0000000..f09d964 --- /dev/null +++ b/middleware/role.js @@ -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() + } +} diff --git a/package.json b/package.json index 99486e0..2d0624e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a320a24..709e39b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -15,6 +15,7 @@ model User { email String @unique password String name String + role Role @default(USER) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tasks Task[] @@ -22,6 +23,11 @@ model User { @@map("users") } +enum Role { + USER + ADMIN +} + model Task { id String @id @default(cuid()) title String diff --git a/routes/auth.js b/routes/auth.js index 7a78cfc..bca0af5 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -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); @@ -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); @@ -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; diff --git a/routes/tasks.js b/routes/tasks.js index dca9b03..5a08095 100644 --- a/routes/tasks.js +++ b/routes/tasks.js @@ -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(); @@ -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); @@ -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);