Skip to content

Commit 40cdf50

Browse files
committed
middleware updated roles added
1 parent e34d582 commit 40cdf50

File tree

3 files changed

+103
-51
lines changed

3 files changed

+103
-51
lines changed

backend/src/app.ts

Lines changed: 67 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,32 @@ import authRoutes from "./routes/auth.js";
1919
import auctionRoutes from "./routes/auctionRoutes";
2020
import paymentRoutes from "./routes/paymentRoutes";
2121

22-
import { signAccessToken, signRefreshToken, verifyRefreshToken } from "./controllers/auth.js";
23-
import { authenticate, AuthRequest } from "./middleware/authMiddleware.js";
22+
import {
23+
signAccessToken,
24+
signRefreshToken,
25+
verifyRefreshToken
26+
} from "./controllers/auth.js";
27+
28+
import {
29+
authenticate,
30+
authorizeRole,
31+
authorizeRoles,
32+
AuthRequest
33+
} from "./middleware/authMiddleware.js";
2434

2535
dotenv.config();
2636

2737
const app: Application = express();
2838

29-
// Middleware
39+
// ------------------- MIDDLEWARE -------------------
3040
app.use(cors());
3141
app.use(morgan("dev"));
3242
app.use(bodyParser.json());
3343
app.use(bodyParser.urlencoded({ extended: true }));
3444
app.use(cookieParser());
35-
3645
app.use(requestLogger);
37-
// Interface and mock data (temporary)
46+
47+
// ------------------- MOCK USER DATA -------------------
3848
interface User {
3949
id: string;
4050
username: string;
@@ -46,7 +56,13 @@ interface User {
4656
}
4757

4858
const users: User[] = [
49-
{ id: "1", username: "alice", passwordHash: bcrypt.hashSync("password", 8), role: "admin", email: "[email protected]" },
59+
{
60+
id: "1",
61+
username: "alice",
62+
passwordHash: bcrypt.hashSync("password", 8),
63+
role: "admin",
64+
65+
},
5066
];
5167

5268
const refreshTokens = new Map<string, string>();
@@ -55,6 +71,7 @@ const refreshTokens = new Map<string, string>();
5571

5672
app.post("/login", async (req: Request, res: Response) => {
5773
const { username, password } = req.body;
74+
5875
const user = users.find((u) => u.username === username);
5976
if (!user) return res.status(401).json({ error: "Invalid credentials" });
6077

@@ -65,10 +82,8 @@ app.post("/login", async (req: Request, res: Response) => {
6582
const accessToken = signAccessToken(payload);
6683
const refreshToken = signRefreshToken(payload);
6784

68-
69-
// app.get("/protected", authenticate, (req: AuthRequest, res: Response) => {
70-
// res.json({ message: "Protected route accessed", user: req.user });
71-
// });
85+
// Store refresh token for session control
86+
refreshTokens.set(user.id, refreshToken);
7287

7388
res.json({ accessToken });
7489
});
@@ -84,13 +99,12 @@ app.post("/refresh", (req: Request, res: Response) => {
8499
const payload = verifyRefreshToken(token);
85100
const storedToken = refreshTokens.get(payload.sub);
86101

87-
if (!storedToken) {
88-
return res.status(401).json({ error: "Session not found or already logged out" });
89-
}
102+
// Validate session
103+
if (!storedToken)
104+
return res.status(401).json({ error: "Session not found or expired" });
90105

91-
if (storedToken !== token) {
92-
return res.status(401).json({ error: "Token used is not the latest valid token" });
93-
}
106+
if (storedToken !== token)
107+
return res.status(401).json({ error: "Invalid refresh token" });
94108

95109
const cleanPayload = {
96110
sub: payload.sub,
@@ -111,9 +125,10 @@ app.post("/refresh", (req: Request, res: Response) => {
111125
});
112126

113127
res.json({ accessToken: newAccess });
128+
114129
} catch (error) {
115-
console.error("Refresh token verification failed:", error);
116-
res.status(401).json({ error: "Refresh token is expired or invalid" });
130+
console.error("Refresh token error:", error);
131+
res.status(401).json({ error: "Refresh token invalid or expired" });
117132
}
118133
});
119134

@@ -123,16 +138,13 @@ app.post("/logout", authenticate, (req: AuthRequest, res: Response) => {
123138
res.status(204).send();
124139
});
125140

141+
// Example protected route
126142
app.get("/protected", authenticate, (req: AuthRequest, res: Response) => {
127143
res.json({ message: "Protected route accessed", user: req.user });
128144
});
129145

130146
// ------------------- PASSWORD RESET ROUTES -------------------
131147

132-
/**
133-
* Step 1: Request password reset
134-
* Generates token, sets expiry, and emails reset link.
135-
*/
136148
app.post("/api/request-reset", async (req: Request, res: Response) => {
137149
const { email } = req.body;
138150
const user = users.find((u) => u.email === email);
@@ -143,16 +155,13 @@ app.post("/api/request-reset", async (req: Request, res: Response) => {
143155

144156
const token = crypto.randomBytes(32).toString("hex");
145157
user.resetPasswordToken = token;
146-
user.resetPasswordExpires = new Date(Date.now() + 3600000); // 1 hour
158+
user.resetPasswordExpires = new Date(Date.now() + 3600000);
147159

148-
const resetLink = `${process.env.FRONTEND_URL || "http://localhost:5173"}/reset-password/${token}`;
160+
const resetLink = `${process.env.FRONTEND_URL}/reset-password/${token}`;
149161

150162
const transporter = nodemailer.createTransport({
151163
service: "gmail",
152-
auth: {
153-
user: process.env.EMAIL_USER,
154-
pass: process.env.EMAIL_PASS,
155-
},
164+
auth: { user: process.env.EMAIL_USER, pass: process.env.EMAIL_PASS }
156165
});
157166

158167
const mailOptions = {
@@ -166,21 +175,20 @@ app.post("/api/request-reset", async (req: Request, res: Response) => {
166175
await transporter.sendMail(mailOptions);
167176
res.json({ msg: "Reset link sent to email" });
168177
} catch (error) {
169-
console.error("Email send failed:", error);
170-
res.status(500).json({ msg: "Failed to send reset email" });
178+
console.error("Email error:", error);
179+
res.status(500).json({ msg: "Failed to send email" });
171180
}
172181
});
173182

174-
/**
175-
* Step 2: Reset password with token
176-
* Validates token, hashes new password, updates it.
177-
*/
178183
app.post("/api/reset-password/:token", async (req: Request, res: Response) => {
179184
const { token } = req.params;
180185
const { password } = req.body;
181186

182187
const user = users.find(
183-
(u) => u.resetPasswordToken === token && u.resetPasswordExpires && u.resetPasswordExpires > new Date()
188+
(u) =>
189+
u.resetPasswordToken === token &&
190+
u.resetPasswordExpires &&
191+
u.resetPasswordExpires > new Date()
184192
);
185193

186194
if (!user) {
@@ -194,33 +202,46 @@ app.post("/api/reset-password/:token", async (req: Request, res: Response) => {
194202
res.json({ msg: "Password updated successfully" });
195203
});
196204

197-
// ------------------- ROUTES -------------------
205+
// ------------------- ROUTES WITH RBAC -------------------
198206

207+
// Public
199208
app.use("/api/health", healthRoutes);
200-
app.use("/api/products", productRoutes);
201-
app.use("/api/users", userRoutes);
202-
app.use("/api/cart", cartRoutes);
203-
app.use("/api/auctions", auctionRoutes);
204-
app.use("/api/payments", paymentRoutes);
205-
app.use("/api", authRoutes);
206209

207-
// ------------------- DEFAULT -------------------
210+
// Products — junior, senior, admin
211+
app.use("/api/products", authenticate, authorizeRoles("junior", "senior", "admin"), productRoutes);
212+
213+
// Users — admin only
214+
app.use("/api/users", authenticate, authorizeRole("admin"), userRoutes);
215+
216+
// Cart — all authenticated roles
217+
app.use("/api/cart", authenticate, authorizeRoles("junior", "senior", "admin"), cartRoutes);
208218

219+
// Auctions — senior + admin
220+
app.use("/api/auctions", authenticate, authorizeRoles("senior", "admin"), auctionRoutes);
221+
222+
// Payments — senior + admin
223+
app.use("/api/payments", authenticate, authorizeRoles("senior", "admin"), paymentRoutes);
224+
225+
// Auth
226+
app.use("/api", authRoutes);
227+
228+
// ------------------- DEFAULT ROUTE -------------------
209229
app.get("/", (_req, res) => {
210230
res.send("API is running");
211231
});
212-
app.use("/api/users", userRoutes);
213232

214233
// ------------------- DATABASE -------------------
215-
216234
const mongoUri = process.env.MONGO_URI;
235+
217236
if (mongoUri && (mongoUri.startsWith("mongodb://") || mongoUri.startsWith("mongodb+srv://"))) {
218237
mongoose
219238
.connect(mongoUri)
220239
.then(() => console.log("MongoDB connected"))
221240
.catch((err) => console.error("MongoDB connection error:", err));
222241
} else {
223-
console.warn("MONGO_URI not set or invalid. Skipping MongoDB connection. Set MONGO_URI in .env to enable DB.");
242+
console.warn("Invalid or missing MONGO_URI.");
224243
}
244+
225245
app.use(errorHandler);
246+
226247
export default app;

backend/src/middleware/authMiddleware.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ import jwt from "jsonwebtoken";
33
const SECRET_KEY = process.env.JWT_SECRET || "your_secret_key";
44

55
export interface AuthenticatedRequest extends Request {
6-
user?: any;
6+
user?: any; // ← keeping your existing type
77
}
88

9+
// ----------------------------
10+
// 🔒 AUTHENTICATE (UNCHANGED)
11+
// ----------------------------
912
export const authenticate = (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
1013
const authHeader = req.headers.authorization;
1114
if (!authHeader || !authHeader.startsWith("Bearer ")) {
@@ -21,10 +24,12 @@ export const authenticate = (req: AuthenticatedRequest, res: Response, next: Nex
2124
}
2225
};
2326

24-
// For legacy support in /api/users route expecting 'protect'
27+
// For legacy support in /api/users route expecting 'protect'
2528
export const protect = authenticate;
2629

27-
// Role-based guard
30+
// -----------------------------------
31+
// 🔐 ORIGINAL authorizeRole (KEPT)
32+
// -----------------------------------
2833
export const authorizeRole = (role: string) => {
2934
return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
3035
if (!req.user) {
@@ -35,4 +40,24 @@ export const authorizeRole = (role: string) => {
3540
}
3641
next();
3742
};
38-
};
43+
};
44+
45+
// ---------------------------------------------------------------
46+
// ⭐ NEW: MULTI-ROLE SUPPORT (Only added, nothing removed above)
47+
// ---------------------------------------------------------------
48+
export const authorizeRoles = (...roles: string[]) => {
49+
return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
50+
51+
if (!req.user) {
52+
return res.status(401).json({ error: "Not authenticated" });
53+
}
54+
55+
if (!roles.includes(req.user.role)) {
56+
return res.status(403).json({
57+
error: `Forbidden: allowed roles are ${roles.join(", ")}`
58+
});
59+
}
60+
61+
next();
62+
};
63+
};

backend/src/models/user.model.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,25 @@ export interface IUser extends Document {
1010
verificationToken?: string;
1111
resetPasswordToken?: string;
1212
resetPasswordExpires?: Date;
13+
role: "junior" | "senior";
1314
comparePassword(candidatePassword: string): Promise<boolean>;
1415
}
1516

1617
const userSchema = new Schema<IUser>(
1718
{
1819
name: { type: String, required: true },
1920
email: { type: String, required: true, unique: true },
20-
password: { type: String, required: function() { return !this.googleId; } },
21+
password: { type: String, required: function () { return !this.googleId; } },
2122
googleId: { type: String, unique: true, sparse: true },
2223
isVerified: { type: Boolean, default: false },
2324
verificationToken: { type: String },
2425
resetPasswordToken: { type: String },
2526
resetPasswordExpires: { type: Date },
27+
role: {
28+
type: String,
29+
enum: ["junior", "senior"],
30+
default: "junior",
31+
},
2632
},
2733
{ timestamps: true }
2834
);

0 commit comments

Comments
 (0)