Skip to content
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
23 changes: 22 additions & 1 deletion backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
"jsonwebtoken": "^9.0.2",
"mongodb": "^6.20.0",
"mongoose": "^8.19.2",
"socket.io": "^4.8.1"
"socket.io": "^4.8.1",
"zod": "^4.1.12"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/express": "^5.0.3",
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "^24.9.1",
"@types/socket.io": "^3.0.1",
"nodemon": "^3.1.10",
"ts-node": "^10.9.2",
"ts-node-dev": "^2.0.0",
Expand Down
88 changes: 44 additions & 44 deletions backend/src/controllers/authController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,59 @@ import type { Request, Response, NextFunction } from "express";
import bcrypt from "bcryptjs";
import User from "../models/userModel.js";
import { generateToken } from "../utils/generateToken.js";
import { validateEmail, validatePassword } from "../utils/validateInputs.js";
import { userSchema, loginSchema } from "../utils/validateInputs.js";

export const registerUser = async (req: Request, res: Response, next: NextFunction) => {
try {
const { name, email, password } = req.body;
try {
const parseResult = userSchema.safeParse(req.body);

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

if (!validateEmail(email))
return res.status(400).json({ success: false, message: "Invalid email format" });
if (!parseResult.success) {
return res.status(400).json({ success: false, message: parseResult.error.issues[0]?.message });
}

if (!validatePassword(password))
return res.status(400).json({ success: false, message: "Password must be at least 6 characters" });
const { name, email, password } = parseResult.data;

const existingUser = await User.findOne({ email });
if (existingUser)
return res.status(400).json({ success: false, message: "Email already registered" });
const existingUser = await User.findOne({ email });
if (existingUser)
return res.status(400).json({ success: false, message: "Email already registered" });

const hashedPassword = await bcrypt.hash(password, 10);
const user = await User.create({ name, email, password: hashedPassword });
const hashedPassword = await bcrypt.hash(password, 10);
const user = await User.create({ name, email, password: hashedPassword });

res.status(201).json({
success: true,
message: "User registered successfully",
token: generateToken(user._id.toString()),
});
} catch (err) {
next(err);
}
res.status(201).json({
success: true,
message: "User registered successfully",
token: generateToken(user._id.toString()),
});
} catch (err) {
next(err);
}
};

export const loginUser = async (req: Request, res: Response, next: NextFunction) => {
try {
const { email, password } = req.body;

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

const user = await User.findOne({ email });
if (!user)
return res.status(400).json({ success: false, message: "Invalid credentials" });

const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch)
return res.status(400).json({ success: false, message: "Invalid credentials" });

res.json({
success: true,
message: "Login successful",
token: generateToken(user._id.toString()),
});
} catch (err) {
next(err);
try {
const parseResult = loginSchema.safeParse(req.body);

if (!parseResult.success) {
return res.status(400).json({ success: false, message: parseResult.error.issues[0]?.message || "Validation error" });
}

const { email, password } = parseResult.data;

const user = await User.findOne({ email });
if (!user)
return res.status(400).json({ success: false, message: "Invalid credentials" });

const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch)
return res.status(400).json({ success: false, message: "Invalid credentials" });

res.json({
success: true,
message: "Login successful",
token: generateToken(user._id.toString()),
});
} catch (err) {
next(err);
}
};
20 changes: 18 additions & 2 deletions backend/src/utils/validateInputs.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
export const validateEmail = (email: string) => /\S+@\S+\.\S+/.test(email);
import { z } from "zod";

export const validatePassword = (password: string) => password.length >= 6;
export const userSchema = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().refine((val) => /^\S+@\S+\.\S+$/.test(val), {
message: "Invalid email format",
}),
password: z.string().min(6, "Password must be at least 6 characters"),
});

export const loginSchema = z.object({
email: z.string().refine((val) => /^\S+@\S+\.\S+$/.test(val), {
message: "Invalid email format",
}),
password: z.string().min(6, "Password must be at least 6 characters"),
});

export type RegisterInput = z.infer<typeof userSchema>;
export type LoginInput = z.infer<typeof loginSchema>;
4 changes: 2 additions & 2 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"autoprefixer": "^10.4.21",
"postcss": "^8.5.6",
"rollup": "^4.52.5",
"tailwindcss": "^4.1.14"
"tailwindcss": "^4.1.14",
"vite": "^7.1.11"
}
}
Loading