Fixes for Admin Login, @somaiya.edu validation, and Password Visibility

This commit is contained in:
arav
2026-01-14 02:47:28 +05:30
parent fe772067dd
commit a114719e00
9 changed files with 229 additions and 71 deletions

View File

@@ -40,6 +40,9 @@ const CourseForm = () => {
const [errors, setErrors] = useState({}); const [errors, setErrors] = useState({});
// State to check if the form is currently submitting
const [loading, setLoading] = useState(false);
useEffect(() => { useEffect(() => {
const fetchOptionsAndFaculties = async () => { const fetchOptionsAndFaculties = async () => {
try { try {
@@ -72,11 +75,11 @@ const CourseForm = () => {
const handleAddFaculty = (field) => { const handleAddFaculty = (field) => {
if (!formData[field]) return; if (!formData[field]) return;
const selectedFaculty = options.faculties.find( const selectedFaculty = options.faculties.find(
(faculty) => faculty.name === formData[field] (faculty) => faculty.name === formData[field]
); );
if (selectedFaculty) { if (selectedFaculty) {
setTempAssignments((prev) => ({ setTempAssignments((prev) => ({
...prev, ...prev,
@@ -85,7 +88,6 @@ const CourseForm = () => {
setFormData({ ...formData, [field]: "" }); setFormData({ ...formData, [field]: "" });
} }
}; };
const handleRemoveFaculty = (field, index) => { const handleRemoveFaculty = (field, index) => {
setTempAssignments((prev) => { setTempAssignments((prev) => {
@@ -139,6 +141,10 @@ const CourseForm = () => {
}); });
const payload = Object.values(groupedTasks); const payload = Object.values(groupedTasks);
// Start loading so user knows it is saving
setLoading(true);
await saveAppointment(payload); await saveAppointment(payload);
// await updateCourseStatus(course?.courseId || id); // await updateCourseStatus(course?.courseId || id);
@@ -152,6 +158,8 @@ const CourseForm = () => {
}); });
} catch (error) { } catch (error) {
console.error("Failed to save appointment:", error); console.error("Failed to save appointment:", error);
// If error comes, stop the loading
setLoading(false);
} }
} }
}; };
@@ -343,13 +351,15 @@ const CourseForm = () => {
type="submit" type="submit"
className="courseFormButton" className="courseFormButton"
style={{ gridColumn: "1 / -1" }} style={{ gridColumn: "1 / -1" }}
disabled={loading} // Disable button when loading
> >
Submit {/* Change text based on loading state */}
{loading ? "Saving..." : "Submit"}
</button> </button>
</form> </form>
</div> </div>
</div> </div>
<Footer/> <Footer />
</div> </div>
</> </>
); );

View File

@@ -1,6 +1,7 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Container, Col, Row } from "react-bootstrap"; import { Container, Col, Row } from "react-bootstrap";
import { FcGoogle } from "react-icons/fc"; import { FcGoogle } from "react-icons/fc";
import { FaEye, FaEyeSlash } from "react-icons/fa";
import axios from "axios"; import axios from "axios";
import md5 from "md5"; import md5 from "md5";
@@ -39,18 +40,26 @@ function AuthPage() {
async function handleSubmit(event) { async function handleSubmit(event) {
event.preventDefault(); event.preventDefault();
// check if username is there
if (!formData.username.trim() && signin) { if (!formData.username.trim() && signin) {
notifyError("Username cannot be empty"); notifyError("Username cannot be empty");
return; return;
} }
// check email format
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!emailRegex.test(formData.email)) { if (!emailRegex.test(formData.email)) {
notifyError("Enter a valid email address"); notifyError("Enter a valid email address");
return; return;
} }
// Check password length // restrict to somaiya emails
if (formData.email.endsWith("@somaiya.edu") === false) {
notifyError("Only @somaiya.edu emails are allowed");
return;
}
// password validation
if (formData.password.length < 8) { if (formData.password.length < 8) {
notifyError("Password must be at least 8 characters long"); notifyError("Password must be at least 8 characters long");
return; return;
@@ -164,12 +173,17 @@ function TogglerContainer(props) {
} }
function SignUpForm(props) { function SignUpForm(props) {
const [showPassword, setShowPassword] = useState(false);
return ( return (
<> <>
<form> <form onSubmit={props.handleSubmit}>
<h1>Create Account</h1> <h1>Create Account</h1>
<div className="Googlediv"> <div className="Googlediv">
<button className="GoogleBtn" onClick={props.handleGoogleLogin}> <button
type="button"
className="GoogleBtn"
onClick={props.handleGoogleLogin}
>
<FcGoogle className="icon" /> Sign up with Google <FcGoogle className="icon" /> Sign up with Google
</button> </button>
</div> </div>
@@ -190,27 +204,48 @@ function SignUpForm(props) {
placeholder="Email" placeholder="Email"
required required
/> />
<input <div style={{ position: "relative", width: "100%" }}>
type="password" <input
name="password" type={showPassword ? "text" : "password"}
value={props.formData.password} name="password"
onChange={props.handleInputChange} value={props.formData.password}
placeholder="Password" onChange={props.handleInputChange}
required placeholder="Password"
/> required
<button onClick={props.handleSubmit}>Sign Up</button> style={{ paddingRight: "40px" }}
/>
<span
onClick={() => setShowPassword(!showPassword)}
style={{
position: "absolute",
right: "10px",
top: "50%",
transform: "translateY(-50%)",
cursor: "pointer",
color: "#666",
}}
>
{showPassword ? <FaEyeSlash /> : <FaEye />}
</span>
</div>
<button type="submit">Sign Up</button>
</form> </form>
</> </>
); );
} }
function SignInForm(props) { function SignInForm(props) {
const [showPassword, setShowPassword] = useState(false);
return ( return (
<> <>
<form> <form onSubmit={props.handleSubmit}>
<h1>Sign In</h1> <h1>Sign In</h1>
<div> <div>
<button className="GoogleBtn" onClick={props.handleGoogleLogin}> <button
type="button"
className="GoogleBtn"
onClick={props.handleGoogleLogin}
>
<FcGoogle className="icon" /> Sign in with Google <FcGoogle className="icon" /> Sign in with Google
</button> </button>
</div> </div>
@@ -223,19 +258,35 @@ function SignInForm(props) {
placeholder="Email" placeholder="Email"
required required
/> />
<input <div style={{ position: "relative", width: "100%" }}>
type="password" <input
name="password" type={showPassword ? "text" : "password"}
value={props.formData.password} name="password"
onChange={props.handleInputChange} value={props.formData.password}
placeholder="Password" onChange={props.handleInputChange}
required placeholder="Password"
/> required
style={{ paddingRight: "40px" }}
/>
<span
onClick={() => setShowPassword(!showPassword)}
style={{
position: "absolute",
right: "10px",
top: "50%",
transform: "translateY(-50%)",
cursor: "pointer",
color: "#666",
}}
>
{showPassword ? <FaEyeSlash /> : <FaEye />}
</span>
</div>
<a href="/ForgetPw">Forget Your Password?</a> <a href="/ForgetPw">Forget Your Password?</a>
<button onClick={props.handleSubmit}>Sign In</button> <button type="submit">Sign In</button>
</form> </form>
</> </>
); );
} }
export default AuthPage; export default AuthPage;

View File

@@ -28,7 +28,7 @@ const WelcomeWithFilter = () => {
</div> </div>
</div> </div>
</div> </div>
<Footer/> <Footer />
</div> </div>
); );
}; };

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

View File

@@ -13,6 +13,14 @@ passport.use(
}, },
async (accessToken, refreshToken, profile, done) => { async (accessToken, refreshToken, profile, done) => {
try { try {
// Security: Check if the email is from Somaiya
// We only want somaiya students/faculty to access this
if (profile.emails[0].value.endsWith("@somaiya.edu") === false) {
return done(null, false, {
message: "Only @somaiya.edu emails are allowed",
});
}
// Check if a user with the same email already exists // Check if a user with the same email already exists
let user = await User.findOne({ email: profile.emails[0].value }); let user = await User.findOne({ email: profile.emails[0].value });

View File

@@ -0,0 +1,25 @@
const jwt = require("jsonwebtoken");
const verifyAdmin = (req, res, next) => {
try {
const token = req.cookies.token; // Ensure you are using cookies for auth
if (!token) {
return res
.status(401)
.json({ message: "Access denied. No token provided." });
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Check if user is admin based on the 'isAdmin' boolean in the token
if (!decoded.isAdmin) {
return res.status(403).json({ message: "Access denied. Admins only." });
}
req.user = decoded; // Attach user data to the request
next();
} catch (error) {
res.status(401).json({ message: "Invalid or expired token" });
}
};
module.exports = verifyAdmin;

View File

@@ -29,7 +29,7 @@
"express": "^4.19.2", "express": "^4.19.2",
"express-session": "^1.18.0", "express-session": "^1.18.0",
"googleapis": "^134.0.0", "googleapis": "^134.0.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.3",
"mongodb": "^6.15.0", "mongodb": "^6.15.0",
"mongoose": "^8.9.5", "mongoose": "^8.9.5",
"mongoose-findorcreate": "^4.0.0", "mongoose-findorcreate": "^4.0.0",
@@ -42,6 +42,8 @@
"uuid": "^11.0.3" "uuid": "^11.0.3"
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^3.1.0" "jest": "^30.2.0",
"nodemon": "^3.1.0",
"supertest": "^7.2.2"
} }
} }

View File

@@ -1,6 +1,6 @@
const express = require("express"); const express = require("express");
const Course = require("../models/Course"); const Course = require("../models/Course");
const verifyAdmin = require("../../client/src/components/verifyAdmin"); const verifyAdmin = require("../middleware/verifyAdmin");
const router = express.Router(); const router = express.Router();
@@ -21,7 +21,6 @@ router.get("/", async (req, res) => {
} }
}); });
// Get course by ID // Get course by ID
router.get("/:id", async (req, res) => { router.get("/:id", async (req, res) => {
try { try {
@@ -88,10 +87,12 @@ router.put("/:courseId", verifyAdmin, async (req, res) => {
} }
}); });
//delete //delete
router.delete("/:courseId", verifyAdmin, async (req, res) => { router.delete("/:courseId", verifyAdmin, async (req, res) => {
try { try {
const deletedCourse = await Course.findOneAndDelete({ courseId: req.params.courseId }); const deletedCourse = await Course.findOneAndDelete({
courseId: req.params.courseId,
});
if (!deletedCourse) { if (!deletedCourse) {
return res.status(404).json({ error: "Course not found" }); return res.status(404).json({ error: "Course not found" });
} }
@@ -105,7 +106,8 @@ router.delete("/:courseId", verifyAdmin, async (req, res) => {
// add a new course // add a new course
router.post("/", verifyAdmin, async (req, res) => { router.post("/", verifyAdmin, async (req, res) => {
try { try {
const { courseId, name, department, program, scheme, semester, status } = req.body; const { courseId, name, department, program, scheme, semester, status } =
req.body;
// Check if a course with the same courseId already exists // Check if a course with the same courseId already exists
const existingCourse = await Course.findOne({ courseId }); const existingCourse = await Course.findOne({ courseId });
@@ -120,17 +122,17 @@ router.post("/", verifyAdmin, async (req, res) => {
program, program,
scheme, scheme,
semester, semester,
status status,
}); });
await newCourse.save(); await newCourse.save();
res.status(201).json({ message: "Course added successfully", course: newCourse }); res
.status(201)
.json({ message: "Course added successfully", course: newCourse });
} catch (error) { } catch (error) {
console.error("Error adding course:", error); console.error("Error adding course:", error);
res.status(500).json({ error: "Failed to add course" }); res.status(500).json({ error: "Failed to add course" });
} }
}); });
module.exports = router; module.exports = router;

View File

@@ -22,20 +22,32 @@ const Course = require("./models/Course");
const User = require("./models/User"); const User = require("./models/User");
// MongoDB Connection // MongoDB Connection
mongoose // MongoDB Connection
.connect(process.env.mongoURI, { useNewUrlParser: true, useUnifiedTopology: true }) const connectDB = async () => {
.then(() => console.log("MongoDB connected")) try {
.catch((err) => { await mongoose.connect(process.env.mongoURI, {
console.error("MongoDB connection error:", err); useNewUrlParser: true,
process.exit(1); // Exit the app if the database connection fails useUnifiedTopology: true,
}); });
console.log("MongoDB connected");
} catch (err) {
console.error("MongoDB connection failed:", err.message);
}
};
connectDB();
// Initialize App // Initialize App
const app = express(); const app = express();
const PORT = 8080; const PORT = 8080;
// Middleware // Middleware
app.use(cors({ origin: process.env.CORS_ORIGIN || "http://localhost:3000", credentials: true })); app.use(
cors({
origin: process.env.CORS_ORIGIN || "http://localhost:3000",
credentials: true,
})
);
app.use(cookieparser()); app.use(cookieparser());
app.use(express.json()); app.use(express.json());
app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.urlencoded({ extended: true }));
@@ -74,9 +86,19 @@ app.get(
"/auth/google/callback", "/auth/google/callback",
passport.authenticate("google", { failureRedirect: "/" }), passport.authenticate("google", { failureRedirect: "/" }),
(req, res) => { (req, res) => {
const token = jwt.sign({ userId: req.user._id }, process.env.JWT_SECRET, { expiresIn: "1h" }); const token = jwt.sign(
{ userId: req.user._id, isAdmin: req.user.isAdmin },
process.env.JWT_SECRET,
{
expiresIn: "1h",
}
);
// Set token as a cookie or send it in the response // Set token as a cookie or send it in the response
res.cookie("token", token, { httpOnly: true, secure: false, maxAge: 3600000 }); res.cookie("token", token, {
httpOnly: true,
secure: false,
maxAge: 3600000,
});
res.redirect("http://localhost:3000/Welcome"); // Redirect to a frontend route after successful login res.redirect("http://localhost:3000/Welcome"); // Redirect to a frontend route after successful login
} }
); );
@@ -85,6 +107,14 @@ app.get(
app.post("/api/register", async (req, res) => { app.post("/api/register", async (req, res) => {
try { try {
const { username, email, password } = req.body; const { username, email, password } = req.body;
// Validation: Only allow somaiya emails
if (email.endsWith("@somaiya.edu") === false) {
return res
.status(400)
.json({ message: "Only @somaiya.edu emails are allowed" });
}
const hashedPassword = await bcrypt.hash(password, 10); const hashedPassword = await bcrypt.hash(password, 10);
let user = await User.findOne({ email }); let user = await User.findOne({ email });
@@ -104,11 +134,21 @@ app.post("/api/register", async (req, res) => {
await user.save(); await user.save();
} }
// Generate a JWT token using the user's ID // adding isAdmin to token so we know if user is admin
const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: "1h" }); const token = jwt.sign(
{ userId: user._id, isAdmin: user.isAdmin },
process.env.JWT_SECRET,
{
expiresIn: "1h",
}
);
// Set the token as a cookie // Set the token as a cookie
res.cookie("token", token, { httpOnly: true, secure: false, maxAge: 3600000 }); // 1 hour expiry res.cookie("token", token, {
httpOnly: true,
secure: false,
maxAge: 3600000,
}); // 1 hour expiry
return res.status(200).json({ return res.status(200).json({
message: "Registered and logged in successfully", message: "Registered and logged in successfully",
@@ -133,10 +173,20 @@ app.post("/api/login", (req, res, next) => {
return res.status(500).json({ message: "Internal server error" }); return res.status(500).json({ message: "Internal server error" });
} }
// Generate a JWT token using the user's ID // Generate a JWT token using the user's ID
const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: "1h" }); const token = jwt.sign(
{ userId: user._id, isAdmin: user.isAdmin },
process.env.JWT_SECRET,
{
expiresIn: "1h",
}
);
// Set the token as a cookie // Set the token as a cookie
res.cookie("token", token, { httpOnly: true, secure: false, maxAge: 3600000 }); // 1 hour expiry res.cookie("token", token, {
httpOnly: true,
secure: false,
maxAge: 3600000,
}); // 1 hour expiry
return res.status(200).json({ message: "Login successful", user }); return res.status(200).json({ message: "Login successful", user });
}); });
})(req, res, next); })(req, res, next);
@@ -158,7 +208,9 @@ app.get("/auth/logout", function (req, res) {
console.error("Error destroying session:", err); console.error("Error destroying session:", err);
return res.status(500).json({ message: "Error logging out" }); return res.status(500).json({ message: "Error logging out" });
} }
res.status(200).json({ message: "Logout successful, session destroyed" }); res
.status(200)
.json({ message: "Logout successful, session destroyed" });
}); });
} else { } else {
// If no session, simply respond with success // If no session, simply respond with success
@@ -175,12 +227,18 @@ app.post("/api/refresh", (req, res) => {
const refreshToken = req.cookies.token; const refreshToken = req.cookies.token;
if (!refreshToken) { if (!refreshToken) {
return res.status(401).json({ message: "No refresh token, authorization denied" }); return res
.status(401)
.json({ message: "No refresh token, authorization denied" });
} }
try { try {
const decoded = jwt.verify(refreshToken, process.env.JWT_SECRET); const decoded = jwt.verify(refreshToken, process.env.JWT_SECRET);
const newToken = jwt.sign({ userId: decoded.userId }, process.env.JWT_SECRET, { expiresIn: "1h" }); const newToken = jwt.sign(
{ userId: decoded.userId, isAdmin: decoded.isAdmin },
process.env.JWT_SECRET,
{ expiresIn: "1h" }
);
return res return res
.cookie("token", newToken, { httpOnly: true, maxAge: 3600000 }) // Set new access token .cookie("token", newToken, { httpOnly: true, maxAge: 3600000 }) // Set new access token
@@ -196,7 +254,6 @@ app.get("/api/auth-check", (req, res) => {
const token = req.cookies.token; // Retrieve the httpOnly cookie const token = req.cookies.token; // Retrieve the httpOnly cookie
if (!token) { if (!token) {
console.log("Tehehe");
return res.status(401).json({ message: "Unauthorized" }); return res.status(401).json({ message: "Unauthorized" });
} }
@@ -211,7 +268,8 @@ app.get("/api/auth-check", (req, res) => {
app.get("/api/me", async (req, res) => { app.get("/api/me", async (req, res) => {
try { try {
const token = req.cookies.token; // ✅ Get token from request cookies const token = req.cookies.token; // ✅ Get token from request cookies
if (!token) return res.status(401).json({ message: "Unauthorized - No Token" }); if (!token)
return res.status(401).json({ message: "Unauthorized - No Token" });
const decoded = jwt.verify(token, process.env.JWT_SECRET); // ✅ Verify token const decoded = jwt.verify(token, process.env.JWT_SECRET); // ✅ Verify token
@@ -223,16 +281,14 @@ app.get("/api/me", async (req, res) => {
userId: user._id, userId: user._id,
isAdmin: user.isAdmin, // ✅ Return actual `isAdmin` value isAdmin: user.isAdmin, // ✅ Return actual `isAdmin` value
exp: decoded.exp, exp: decoded.exp,
iat: decoded.iat iat: decoded.iat,
}); });
} catch (error) { } catch (error) {
console.error("JWT Verification Error:", error.message); console.error("JWT Verification Error:", error.message);
res.status(401).json({ message: "Invalid token" }); res.status(401).json({ message: "Invalid token" });
} }
}); });
// User Profile Route // User Profile Route
app.get("/api/user/profile", async (req, res) => { app.get("/api/user/profile", async (req, res) => {
try { try {
@@ -251,9 +307,6 @@ app.patch("/api/courses/:courseId", async (req, res) => {
const { courseId } = req.params; const { courseId } = req.params;
const { status } = req.body; const { status } = req.body;
console.log("Request params:", req.params);
console.log("Request body:", req.body);
if (!status) { if (!status) {
console.error("Status is missing in the request body."); console.error("Status is missing in the request body.");
return res.status(400).json({ message: "Status is required" }); return res.status(400).json({ message: "Status is required" });
@@ -287,10 +340,17 @@ app.get("*", (req, res) =>
// Error Handling Middleware // Error Handling Middleware
app.use((err, req, res, next) => { app.use((err, req, res, next) => {
console.error("Error:", err.stack); console.error("Error:", err.stack);
res.status(err.status || 500).json({ error: err.message || "Internal Server Error" }); res
.status(err.status || 500)
.json({ error: err.message || "Internal Server Error" });
}); });
// Start Server // Start Server
app.listen(PORT, () => { // Start Server
console.log(`Server is running at http://localhost:8080`); if (require.main === module) {
}); app.listen(PORT, () => {
console.log(`Server is running at http://localhost:8080`);
});
}
module.exports = app;