Implemented Oauth

This commit is contained in:
2026-01-02 00:36:24 +05:30
parent 933c0741ab
commit 0130f746b0
22 changed files with 960 additions and 257 deletions

View File

@@ -1,29 +1,59 @@
import express from 'express';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import router from './routes/auth.js';
import applicantRoute from './routes/applicant.js';
import validatorRoute from './routes/validator.js';
import generalRoute from './routes/general.js';
import { verifyApplicantToken, verifyToken, verifyValidatorToken } from './middleware/verifyJwt.js';
import express from "express";
import cors from "cors";
import cookieParser from "cookie-parser";
import session from "express-session";
import passport, { initializePassport } from "./services/passportService.js";
import router from "./routes/auth.js";
import applicantRoute from "./routes/applicant.js";
import validatorRoute from "./routes/validator.js";
import generalRoute from "./routes/general.js";
import {
verifyApplicantToken,
verifyToken,
verifyValidatorToken,
} from "./middleware/verifyJwt.js";
// Initialize passport strategies after environment variables are loaded
initializePassport();
const app = express();
// Middleware setup
app.use(cookieParser());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors({
origin: true,
credentials: true
}));
app.use(cookieParser());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(
cors({
origin: process.env.FRONTEND_URL || "http://localhost:5173",
credentials: true,
}),
);
// Session middleware (required for Passport)
app.use(
session({
secret:
process.env.SESSION_SECRET || "your-secret-key-change-this-in-production",
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === "production",
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000, // 24 hours
},
}),
);
// Initialize Passport
app.use(passport.initialize());
app.use(passport.session());
// Route-specific middleware and routes
app.use('/applicant', verifyApplicantToken, applicantRoute);
app.use('/validator', verifyValidatorToken, validatorRoute);
app.use('/general', verifyToken, generalRoute);
app.use("/applicant", verifyApplicantToken, applicantRoute);
app.use("/validator", verifyValidatorToken, validatorRoute);
app.use("/general", verifyToken, generalRoute);
// Authentication routes
app.use(router);
export default app;
export default app;

View File

@@ -1,5 +1,6 @@
import prisma from "../config/prismaConfig.js";
import generateToken from "../services/generateToken.js";
import passport from "passport";
const applicantLogin = async (req, res) => {
try {
@@ -8,7 +9,7 @@ const applicantLogin = async (req, res) => {
// Check if the applicant profile exists
const validProfile = await prisma.user.findUnique({
where: {
email
email,
},
});
@@ -41,7 +42,13 @@ const applicantLogin = async (req, res) => {
// Set the token as a cookie
return res
.cookie("access_token", token, { sameSite: 'None', secure: true, httpOnly: true })
.cookie("access_token", token, {
path: "/",
sameSite: process.env.NODE_ENV === "production" ? "None" : "Lax",
secure: process.env.NODE_ENV === "production",
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000, // 24 hours
})
.status(200)
.json({
message: "Login Successful",
@@ -62,7 +69,7 @@ const validatorLogin = async (req, res) => {
// Check if the validator profile exists
let validProfile = await prisma.user.findUnique({
where: {
email
email,
},
});
@@ -95,7 +102,13 @@ const validatorLogin = async (req, res) => {
// Set the token as a cookie
return res
.cookie("access_token", token, { sameSite: 'None', secure: true, httpOnly: true })
.cookie("access_token", token, {
path: "/",
sameSite: process.env.NODE_ENV === "production" ? "None" : "Lax",
secure: process.env.NODE_ENV === "production",
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000, // 24 hours
})
.status(200)
.json({
message: "Login Successful",
@@ -127,4 +140,67 @@ const logout = async (req, res) => {
}
};
export { applicantLogin, validatorLogin, logout };
//this is the controller which will handle the oauth logic
const googleAuthStart = async (req, res, next) => {
const designation = req.params.designation;
passport.authenticate("google", {
scope: ["profile", "email"],
state: designation,
})(req, res, next);
};
//this is the oauth callback controller
const googleAuthCallback = async (req, res, next) => {
try {
const signUpIntent = req.query.state;
const user = req.user;
const allowedIntents = ["validator", "applicant"];
if (!allowedIntents.includes(signUpIntent)) {
return res.redirect(
`${process.env.FRONTEND_URL || "http://localhost:5173"}/?error=invalid_intent`,
);
}
// Generate the token using correct field names from Prisma schema
const token = generateToken({
id: user.profileId,
designation: user.designation,
department: user.department,
institute: user.institute,
role: signUpIntent,
});
// Set the token as a cookie for same-origin requests
const cookieOptions = {
path: "/",
sameSite: process.env.NODE_ENV === "production" ? "None" : "Lax",
secure: process.env.NODE_ENV === "production",
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000, // 24 hours
};
res.cookie("access_token", token, cookieOptions);
// For OAuth callback, also pass token in URL so frontend can set it
// This is needed because cross-origin cookies don't work in development (different ports)
return res.redirect(
`${process.env.FRONTEND_URL || "http://localhost:5173"}/${signUpIntent}/dashboard?login=success&token=${token}`,
);
} catch (error) {
console.error("OAuth callback error:", error);
return res.redirect(
`${process.env.FRONTEND_URL || "http://localhost:5173"}/?error=auth_failed`,
);
}
};
export {
applicantLogin,
validatorLogin,
logout,
googleAuthStart,
googleAuthCallback,
};

View File

@@ -1,11 +1,30 @@
import express from 'express';
import { applicantLogin, logout, validatorLogin } from '../controllers/authControllers.js';
import express from "express";
import {
applicantLogin,
logout,
validatorLogin,
googleAuthStart,
googleAuthCallback,
} from "../controllers/authControllers.js";
import passport from "../services/passportService.js";
const router = express.Router();
router.post('/applicant-login', applicantLogin);
router.post('/validator-login', validatorLogin);
router.post("/applicant-login", applicantLogin);
//this route is for google oauth, this one route will handle both applicantLogic and validatorLo
// we will be passing the designation as a URL parameter ("validator" or "applicant") and it will be passed as state through OAuth
router.get("/auth/oauth/:designation", googleAuthStart);
//this will be the oauth callback Route
router.get(
"/auth/google/callback",
passport.authenticate("google", {
failureRedirect: "http://localhost:5173/?error=auth_failed",
}),
googleAuthCallback,
);
router.get('/logout', logout)
router.post("/validator", validatorLogin);
export default router;
router.get("/logout", logout);
export default router;

View File

@@ -1,9 +1,26 @@
import app from './app.js';
import dotenv from 'dotenv';
dotenv.config();
import dotenv from "dotenv";
import path from "path";
import { fileURLToPath } from "url";
const port = process.env.PORT || 3000;
// Get the directory name in ES modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
})
// Load .env from backend directory
dotenv.config({ path: path.join(__dirname, "..", ".env") });
// Dynamic import to ensure dotenv loads first
const startServer = async () => {
const { default: app } = await import("./app.js");
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
};
startServer().catch((error) => {
console.error("Failed to start server:", error);
process.exit(1);
});

View File

@@ -0,0 +1,79 @@
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
import prisma from "../config/prismaConfig.js";
import passport from "passport";
// Function to initialize passport strategies
export const initializePassport = () => {
// Validate required environment variables
if (!process.env.GOOGLE_CLIENT_ID || !process.env.GOOGLE_CLIENT_SECRET) {
console.error(
"ERROR: Missing required Google OAuth credentials in environment variables.",
);
console.error(
"Please ensure GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET are set in your .env file",
);
throw new Error("Missing Google OAuth credentials");
}
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: `${process.env.BACKEND_URL || "http://localhost:3000"}/auth/google/callback`,
scope: ["profile", "email"],
},
async (accessToken, refreshToken, profile, done) => {
//checking if theres existing user with email
try {
const existingUser = await prisma.user.findUnique({
where: { email: profile.emails[0]?.value },
});
if (existingUser) {
return done(null, existingUser);
}
const newUser = await prisma.user.create({
data: {
userName: profile.displayName, // I am storing the name , other devs can switch to display_name based on their preferences
email: profile.emails[0].value,
password: "", // OAuth users don't use password authentication
designation: "FACULTY", // Default designation, can be updated later
auth_mode: "Google",
OAuth_AccessToken: accessToken,
OAuth_RefreshToken: refreshToken, //I am saving the accessTokens and refreshTokens, which MIGHT be used later
},
});
console.log(
"Passport service has made a new user: ",
JSON.stringify(newUser),
);
done(null, newUser);
} catch (err) {
console.error("Error creating user:", err);
done(err, null);
}
},
),
);
// Serialize user for session
passport.serializeUser((user, done) => {
done(null, user.profileId);
});
// Deserialize user from session
passport.deserializeUser(async (id, done) => {
try {
const user = await prisma.user.findUnique({
where: { profileId: id },
});
done(null, user);
} catch (error) {
done(error, null);
}
});
return passport;
};
export default passport;