Files

357 lines
10 KiB
JavaScript

const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
const session = require("express-session");
const passport = require("passport");
const bodyParser = require("body-parser");
const path = require("path");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const cookieparser = require("cookie-parser");
require("dotenv").config();
// Import Routes
const authRoutes = require("./routes/authRoutes");
const courseRoutes = require("./routes/courseRoutes");
const facultyRoutes = require("./routes/facultyRoutes");
const appointmentRoutes = require("./routes/appointmentRoutes");
const optionsRoutes = require("./routes/optionsRoutes");
const consolidatedRoutes = require("./routes/consolidatedRoutes");
const emailRoutes = require("./routes/emailRoutes");
const Course = require("./models/Course");
const User = require("./models/User");
// MongoDB Connection
// MongoDB Connection
const connectDB = async () => {
try {
await mongoose.connect(process.env.mongoURI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log("MongoDB connected");
} catch (err) {
console.error("MongoDB connection failed:", err.message);
}
};
connectDB();
// Initialize App
const app = express();
const PORT = 8080;
// Middleware
app.use(
cors({
origin: process.env.CORS_ORIGIN || "http://localhost:3000",
credentials: true,
})
);
app.use(cookieparser());
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(
session({
secret: "secret", // This can be replaced with another secret from .env if required
resave: false,
saveUninitialized: false,
})
);
app.use(passport.initialize());
app.use(passport.session());
app.use("/api/table", consolidatedRoutes);
// Passport Config
require("./config/passport");
// Routes
app.use("/password", authRoutes);
app.use("/api/courses", courseRoutes);
app.use("/api/faculty", facultyRoutes);
app.use("/api/appointments", appointmentRoutes);
app.use("/api/options", optionsRoutes);
app.use("/api/data", consolidatedRoutes);
app.use("/api/send-email", emailRoutes);
// Google OAuth Routes
app.get(
"/auth/google",
passport.authenticate("google", { scope: ["profile", "email"] })
);
app.get(
"/auth/google/callback",
passport.authenticate("google", { failureRedirect: "/" }),
(req, res) => {
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
res.cookie("token", token, {
httpOnly: true,
secure: false,
maxAge: 3600000,
});
res.redirect("http://localhost:3000/Welcome"); // Redirect to a frontend route after successful login
}
);
// Local authentication routes (register and login)
app.post("/api/register", async (req, res) => {
try {
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);
let user = await User.findOne({ email });
if (user) {
if (user.googleId && user.password) {
return res.status(400).json({ message: "User already exists" });
}
if (!user.googleId && user.password) {
return res.status(400).json({ message: "User already exists" });
}
if (user.googleId && !user.password) {
user.password = hashedPassword;
await user.save();
}
} else {
user = new User({ username, email, password: hashedPassword });
await user.save();
}
// adding isAdmin to token so we know if user is admin
const token = jwt.sign(
{ userId: user._id, isAdmin: user.isAdmin },
process.env.JWT_SECRET,
{
expiresIn: "1h",
}
);
// Set the token as a cookie
res.cookie("token", token, {
httpOnly: true,
secure: false,
maxAge: 3600000,
}); // 1 hour expiry
return res.status(200).json({
message: "Registered and logged in successfully",
user,
});
} catch (error) {
console.error("Error registering user:", error);
res.status(400).send("Registration failed");
}
});
app.post("/api/login", (req, res, next) => {
passport.authenticate("local", (err, user, info) => {
if (err) {
return res.status(500).json({ message: "Internal server error" });
}
if (!user) {
return res.status(401).json({ message: "Incorrect email or password" });
}
req.logIn(user, (err) => {
if (err) {
return res.status(500).json({ message: "Internal server error" });
}
// Generate a JWT token using the user's ID
const token = jwt.sign(
{ userId: user._id, isAdmin: user.isAdmin },
process.env.JWT_SECRET,
{
expiresIn: "1h",
}
);
// Set the token as a cookie
res.cookie("token", token, {
httpOnly: true,
secure: false,
maxAge: 3600000,
}); // 1 hour expiry
return res.status(200).json({ message: "Login successful", user });
});
})(req, res, next);
});
app.get("/auth/logout", function (req, res) {
try {
// Clear the token cookie
res.clearCookie("token", {
httpOnly: true, // Ensure it matches the cookie options you set earlier
secure: false, // Match the "secure" option from cookie settings
sameSite: "lax", // Ensure this matches the original cookie configuration
});
// Destroy the session if used (optional, if sessions are implemented)
if (req.session) {
req.session.destroy((err) => {
if (err) {
console.error("Error destroying session:", err);
return res.status(500).json({ message: "Error logging out" });
}
res
.status(200)
.json({ message: "Logout successful, session destroyed" });
});
} else {
// If no session, simply respond with success
res.status(200).json({ message: "Logout successful, cookie cleared" });
}
} catch (err) {
console.error("Error logging out:", err);
res.status(500).json({ message: "Error logging out" });
}
});
// Refresh Token Endpoint
app.post("/api/refresh", (req, res) => {
const refreshToken = req.cookies.token;
if (!refreshToken) {
return res
.status(401)
.json({ message: "No refresh token, authorization denied" });
}
try {
const decoded = jwt.verify(refreshToken, process.env.JWT_SECRET);
const newToken = jwt.sign(
{ userId: decoded.userId, isAdmin: decoded.isAdmin },
process.env.JWT_SECRET,
{ expiresIn: "1h" }
);
return res
.cookie("token", newToken, { httpOnly: true, maxAge: 3600000 }) // Set new access token
.status(200)
.json({ message: "Token refreshed" });
} catch (err) {
console.error("Error refreshing token:", err);
res.status(401).json({ message: "Invalid or expired refresh token" });
}
});
app.get("/api/auth-check", (req, res) => {
const token = req.cookies.token; // Retrieve the httpOnly cookie
if (!token) {
return res.status(401).json({ message: "Unauthorized" });
}
try {
jwt.verify(token, process.env.JWT_SECRET); // Verify the token
res.status(200).json({ authenticated: true }); // Valid token
} catch (err) {
res.status(401).json({ message: "Unauthorized" }); // Invalid token
}
});
app.get("/api/me", async (req, res) => {
try {
const token = req.cookies.token; // ✅ Get token from request cookies
if (!token)
return res.status(401).json({ message: "Unauthorized - No Token" });
const decoded = jwt.verify(token, process.env.JWT_SECRET); // ✅ Verify token
// ✅ Fetch user from DB to ensure latest `isAdmin` value
const user = await User.findById(decoded.userId);
if (!user) return res.status(404).json({ message: "User not found" });
res.json({
userId: user._id,
isAdmin: user.isAdmin, // ✅ Return actual `isAdmin` value
exp: decoded.exp,
iat: decoded.iat,
});
} catch (error) {
console.error("JWT Verification Error:", error.message);
res.status(401).json({ message: "Invalid token" });
}
});
// User Profile Route
app.get("/api/user/profile", async (req, res) => {
try {
if (req.user) {
return res.json({ user: req.user });
} else {
return res.status(401).json({ error: "Unauthorized" });
}
} catch (error) {
console.error("Error fetching user data:", error);
res.status(500).json({ message: "Internal server error" });
}
});
app.patch("/api/courses/:courseId", async (req, res) => {
const { courseId } = req.params;
const { status } = req.body;
if (!status) {
console.error("Status is missing in the request body.");
return res.status(400).json({ message: "Status is required" });
}
try {
const updatedCourse = await Course.findOneAndUpdate(
{ courseId: courseId }, // Use courseId field for finding the course
{ status }, // Update the status field
{ new: true } // Return the updated document
);
if (!updatedCourse) {
console.error("Course not found:", courseId);
return res.status(404).json({ message: "Course not found" });
}
res.json(updatedCourse);
} catch (error) {
console.error("Error updating course status:", error.message);
res.status(500).json({ message: "Internal server error" });
}
});
// Serve React Build Files
app.use(express.static(path.join(__dirname, "../client/build")));
app.get("*", (req, res) =>
res.sendFile(path.join(__dirname, "../client/build/index.html"))
);
// Error Handling Middleware
app.use((err, req, res, next) => {
console.error("Error:", err.stack);
res
.status(err.status || 500)
.json({ error: err.message || "Internal Server Error" });
});
// Start Server
// Start Server
if (require.main === module) {
app.listen(PORT, () => {
console.log(`Server is running at http://localhost:8080`);
});
}
module.exports = app;