Skip to content

Commit 6eb7d5e

Browse files
authored
Merge pull request #60 from ADARSHsri2004/feat/backend_restructure
feat: added proper structure in backend and fixed email verification
2 parents 5cb90d1 + 0676fe5 commit 6eb7d5e

File tree

9 files changed

+178
-296
lines changed

9 files changed

+178
-296
lines changed

backend/package-lock.json

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/src/app.ts

Lines changed: 9 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
1-
import express, { Application, Request, Response } from 'express';
1+
import express from 'express';
22
import cors from 'cors';
33
import morgan from 'morgan';
44
import bodyParser from 'body-parser';
55
import healthRoutes from './routes/healthRoutes';
66
import userRoutes from "./routes/user.routes";
7-
8-
9-
// Consolidated Imports
107
import productRoutes from "./routes/product.routes";
11-
import cartRoutes from "./routes/cartRoutes";
8+
import cartRoutes from "./routes/cartRoutes";
129
import mongoose from "mongoose";
13-
import bcrypt from "bcryptjs";
10+
1411
import cookieParser from "cookie-parser";
15-
import { signAccessToken, signRefreshToken, verifyRefreshToken } from "./controllers/auth";
16-
import { authenticate, AuthRequest } from "./middleware/authMiddleware";
1712

18-
const app: Application = express();
13+
const app = express();
14+
1915
import dotenv from "dotenv";
2016
dotenv.config();
2117

@@ -28,112 +24,19 @@ app.use(bodyParser.urlencoded({ extended: true }));
2824
app.use(cookieParser());
2925

3026

31-
// 3. User Interface and Mock Data
32-
interface User {
33-
id: string;
34-
username: string;
35-
passwordHash: string;
36-
role: string;
37-
}
38-
39-
const users: User[] = [
40-
{ id: "1", username: "alice", passwordHash: bcrypt.hashSync("password", 8), role: "admin" },
41-
];
42-
43-
const refreshTokens = new Map<string, string>()
44-
45-
46-
// 4. Authentication API Routes
47-
app.post("/login", async (req: Request, res: Response) => {
48-
const { username, password } = req.body;
49-
const user = users.find((u) => u.username === username);
50-
if (!user) return res.status(401).json({ error: "Invalid credentials" });
51-
52-
const match = await bcrypt.compare(password, user.passwordHash);
53-
if (!match) return res.status(401).json({ error: "Invalid credentials" });
54-
55-
const payload = { sub: user.id, username: user.username, role: user.role };
56-
const accessToken = signAccessToken(payload);
57-
const refreshToken = signRefreshToken(payload);
5827

59-
refreshTokens.set(user.id, refreshToken);
6028

61-
res.cookie("refreshToken", refreshToken, {
62-
httpOnly: true,
63-
sameSite: "strict",
64-
secure: process.env.NODE_ENV === "production",
65-
maxAge: 7 * 24 * 60 * 60 * 1000,
66-
});
29+
// app.get("/protected", authenticate, (req: AuthRequest, res: Response) => {
30+
// res.json({ message: "Protected route accessed", user: req.user });
31+
// });
6732

68-
res.json({ accessToken });
69-
});
70-
71-
app.post("/refresh", (req: Request, res: Response) => {
72-
const token = req.cookies?.refreshToken || req.body.refreshToken;
73-
74-
if (!token) {
75-
return res.status(401).json({ error: "Refresh token is missing" });
76-
}
77-
78-
try {
79-
const payload = verifyRefreshToken(token);
80-
const storedToken = refreshTokens.get(payload.sub);
81-
82-
if (!storedToken) {
83-
return res.status(401).json({ error: "Session not found or already logged out" });
84-
}
85-
86-
if (storedToken !== token) {
87-
return res.status(401).json({ error: "Token used is not the latest valid token" });
88-
}
89-
90-
const cleanPayload = {
91-
sub: payload.sub,
92-
username: payload.username,
93-
role: payload.role
94-
};
95-
96-
const newAccess = signAccessToken(cleanPayload);
97-
const newRefresh = signRefreshToken(cleanPayload);
98-
99-
refreshTokens.set(cleanPayload.sub, newRefresh);
100-
101-
res.cookie("refreshToken", newRefresh, {
102-
httpOnly: true,
103-
sameSite: "strict",
104-
secure: process.env.NODE_ENV === "production",
105-
maxAge: 7 * 24 * 60 * 60 * 1000,
106-
});
107-
108-
res.json({ accessToken: newAccess });
109-
110-
} catch (error) {
111-
console.error("Refresh token verification failed:", error);
112-
res.status(401).json({ error: "Refresh token is expired or invalid" });
113-
}
114-
});
115-
116-
app.post("/logout", authenticate, (req: AuthRequest, res: Response) => {
117-
// req.user is guaranteed to exist by the 'authenticate' middleware
118-
refreshTokens.delete(req.user!.sub);
119-
res.clearCookie("refreshToken");
120-
res.status(204).send();
121-
});
122-
123-
app.get("/protected", authenticate, (req: AuthRequest, res: Response) => {
124-
res.json({ message: "Protected route accessed", user: req.user });
125-
});
12633

12734
// Existing API Routes
12835
app.use('/api/health', healthRoutes);
12936
app.use("/api/products", productRoutes);
13037
app.use("/api/users", userRoutes);
13138
app.use("/api/cart", cartRoutes);
132-
133-
// Default
134-
app.get("/", (_req, res) => {
135-
res.send("API is running ");
136-
});
39+
app.use("/api/users", userRoutes);
13740

13841
// MongoDB connect (optional)
13942
const mongoUri = process.env.MONGO_URI;
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// src/controllers/authController.ts
2+
import { Request, Response } from "express";
3+
import jwt from "jsonwebtoken";
4+
import crypto from "crypto";
5+
import nodemailer from "nodemailer";
6+
import { User } from "../models/user.model";
7+
8+
const JWT_SECRET = process.env.JWT_SECRET || "secretkey";
9+
const CLIENT_URL = process.env.CLIENT_URL || "http://localhost:3000";
10+
11+
const transporter = nodemailer.createTransport({
12+
// service: "gmail",
13+
// auth: {
14+
// user: process.env.EMAIL_USER,
15+
// pass: process.env.EMAIL_PASS,
16+
// },
17+
host: 'smtp.ethereal.email',
18+
port: 587,
19+
auth: {
20+
21+
pass: 'Du8rSm184HzwwHsHYm'
22+
}
23+
});
24+
25+
const generateToken = (id: string) => {
26+
return jwt.sign({ id }, JWT_SECRET, { expiresIn: "7d" });
27+
};
28+
29+
// Register user
30+
export const register = async (req: Request, res: Response) => {
31+
try {
32+
const { name, email, password } = req.body;
33+
const existingUser = await User.findOne({ email });
34+
if (existingUser) return res.status(400).json({ message: "User already exists" });
35+
36+
const verificationToken = crypto.randomBytes(20).toString("hex");
37+
const user = await User.create({ name, email, password, verificationToken });
38+
39+
const verificationLink = `${CLIENT_URL}/verify/${verificationToken}`;
40+
await transporter.sendMail({
41+
to: email,
42+
subject: "Verify your email",
43+
html: `<p>Click <a href="${verificationLink}">here</a> to verify your account.</p>`,
44+
});
45+
46+
res.status(201).json({ message: "User registered. Check your email for verification link." });
47+
} catch (error) {
48+
res.status(500).json({ message: "Registration failed", error });
49+
}
50+
};
51+
52+
// Verify email
53+
export const verifyEmail = async (req: Request, res: Response) => {
54+
try {
55+
const { token } = req.params;
56+
const user = await User.findOne({ verificationToken: token });
57+
58+
if (!user) return res.status(400).json({ message: "Invalid or expired token" });
59+
60+
user.isVerified = true;
61+
user.verificationToken = undefined;
62+
await user.save();
63+
64+
res.status(200).json({ message: "Email verified successfully" });
65+
} catch (error) {
66+
res.status(500).json({ message: "Verification failed", error });
67+
}
68+
};
69+
70+
// Login
71+
export const login = async (req: Request, res: Response) => {
72+
try {
73+
const { email, password } = req.body;
74+
const user = await User.findOne({ email });
75+
76+
if (!user) return res.status(400).json({ message: "Invalid credentials" });
77+
if (!user.isVerified) return res.status(403).json({ message: "Please verify your email" });
78+
79+
const isMatch = await user.comparePassword(password);
80+
if (!isMatch) return res.status(400).json({ message: "Invalid credentials" });
81+
82+
const token = generateToken(String(user._id));
83+
res.status(200).json({ token, user: { name: user.name, email: user.email } });
84+
} catch (error) {
85+
res.status(500).json({ message: "Login failed", error });
86+
}
87+
};
88+
89+
// Get current user
90+
export const getMe = async (req: Request, res: Response) => {
91+
try {
92+
const userId = (req as any).user?.id;
93+
if (!userId) return res.status(401).json({ message: "Unauthorized" });
94+
95+
const user = await User.findById(userId).select("-password");
96+
if (!user) return res.status(404).json({ message: "User not found" });
97+
98+
res.status(200).json(user);
99+
} catch (error) {
100+
res.status(500).json({ message: "Error fetching user", error });
101+
}
102+
};

backend/src/controllers/auth.ts

Lines changed: 0 additions & 50 deletions
This file was deleted.
Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,28 @@
1-
import { Request, Response, NextFunction } from 'express';
2-
import { verifyAccessToken } from '../controllers/auth';
1+
// src/middlewares/authMiddleware.ts
2+
import { Request, Response, NextFunction } from "express";
3+
import jwt from "jsonwebtoken";
4+
import { User } from "../models/user.model";
35

4-
export interface AuthenticatedRequest extends Request {
6+
const JWT_SECRET = process.env.JWT_SECRET || "secretkey";
7+
8+
export interface AuthRequest extends Request {
59
user?: any;
610
}
711

8-
export type AuthRequest = AuthenticatedRequest;
9-
10-
// (Middleware file - authMiddleware.ts)
12+
export const protect = async (req: AuthRequest, res: Response, next: NextFunction) => {
13+
let token;
1114

12-
export function authenticate(req: AuthenticatedRequest, res: Response, next: NextFunction) {
13-
const header = req.headers.authorization;
14-
const token = header?.startsWith('Bearer ') ? header.slice(7) : undefined;
15-
16-
if (!token) {
17-
// Use 401 for NO credentials (token missing entirely)
18-
return res.status(401).json({ error: 'No token provided' });
15+
if (req.headers.authorization && req.headers.authorization.startsWith("Bearer")) {
16+
token = req.headers.authorization.split(" ")[1];
1917
}
2018

19+
if (!token) return res.status(401).json({ message: "Not authorized, no token" });
20+
2121
try {
22-
const payload = verifyAccessToken(token);
23-
req.user = payload;
22+
const decoded = jwt.verify(token, JWT_SECRET) as { id: string };
23+
req.user = await User.findById(decoded.id).select("-password");
2424
next();
25-
} catch {
26-
// Use 403 for INVALID credentials (token present but invalid/expired/rejected)
27-
// This is often seen as a better status for expired tokens.
28-
return res.status(403).json({ error: 'Invalid or expired token' });
25+
} catch (error) {
26+
res.status(401).json({ message: "Not authorized, token failed" });
2927
}
30-
}
31-
export function authorizeRole(role: string) {
32-
return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
33-
if (!req.user) {
34-
return res.status(401).json({ error: 'Not authenticated' });
35-
}
36-
if (req.user.role !== role) {
37-
return res.status(403).json({ error: 'Forbidden' });
38-
}
39-
next();
40-
};
41-
}
28+
};

0 commit comments

Comments
 (0)