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

28
backend/.env.example Normal file
View File

@@ -0,0 +1,28 @@
# Database
DATABASE_URL="postgresql://user:password@localhost:5432/travel_policy_db?schema=public"
# JWT Secret for token generation
JWT_SECRET="your-secret-jwt-key-here-change-this-in-production"
# Session Secret
SESSION_SECRET="your-session-secret-key-here-change-this-in-production"
# Google OAuth Credentials
# Get these from https://console.cloud.google.com/
GOOGLE_CLIENT_ID="your-google-client-id.apps.googleusercontent.com"
GOOGLE_CLIENT_SECRET="your-google-client-secret"
GOOGLE_CALLBACK_URL="http://localhost:5000/auth/google/callback"
# Frontend URL (for CORS and redirects)
FRONTEND_URL="http://localhost:5173"
# Server Configuration
PORT=5000
NODE_ENV="development"
# Email Configuration (if using nodemailer)
EMAIL_HOST="smtp.gmail.com"
EMAIL_PORT=587
EMAIL_USER="your-email@example.com"
EMAIL_PASSWORD="your-email-password-or-app-password"
EMAIL_FROM="noreply@example.com"

5
backend/.gitignore vendored
View File

@@ -1,2 +1,5 @@
/node_modules
.env
.env
.env.bak2
.env.temp
.env.bak

View File

@@ -15,9 +15,12 @@
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express-session": "^1.18.2",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.16",
"passport": "^0.7.0",
"passport-google-oauth20": "^2.0.0",
"prisma": "^5.20.0"
},
"devDependencies": {
@@ -129,6 +132,15 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/base64url": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz",
"integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==",
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/bcryptjs": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
@@ -515,6 +527,31 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/express-session": {
"version": "1.18.2",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz",
"integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==",
"license": "MIT",
"dependencies": {
"cookie": "0.7.2",
"cookie-signature": "1.0.7",
"debug": "2.6.9",
"depd": "~2.0.0",
"on-headers": "~1.1.0",
"parseurl": "~1.3.3",
"safe-buffer": "5.2.1",
"uid-safe": "~2.1.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/express-session/node_modules/cookie-signature": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
"license": "MIT"
},
"node_modules/express/node_modules/cookie": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
@@ -1059,6 +1096,12 @@
"node": ">=0.10.0"
}
},
"node_modules/oauth": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz",
"integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==",
"license": "MIT"
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -1091,6 +1134,15 @@
"node": ">= 0.8"
}
},
"node_modules/on-headers": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -1100,12 +1152,75 @@
"node": ">= 0.8"
}
},
"node_modules/passport": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz",
"integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==",
"license": "MIT",
"dependencies": {
"passport-strategy": "1.x.x",
"pause": "0.0.1",
"utils-merge": "^1.0.1"
},
"engines": {
"node": ">= 0.4.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/jaredhanson"
}
},
"node_modules/passport-google-oauth20": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz",
"integrity": "sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==",
"license": "MIT",
"dependencies": {
"passport-oauth2": "1.x.x"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/passport-oauth2": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz",
"integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==",
"license": "MIT",
"dependencies": {
"base64url": "3.x.x",
"oauth": "0.10.x",
"passport-strategy": "1.x.x",
"uid2": "0.0.x",
"utils-merge": "1.x.x"
},
"engines": {
"node": ">= 0.4.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/jaredhanson"
}
},
"node_modules/passport-strategy": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
"integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/path-to-regexp": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT"
},
"node_modules/pause": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
@@ -1176,6 +1291,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -1468,6 +1592,24 @@
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
"license": "MIT"
},
"node_modules/uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
"license": "MIT",
"dependencies": {
"random-bytes": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/uid2": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz",
"integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==",
"license": "MIT"
},
"node_modules/undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",

View File

@@ -6,9 +6,12 @@
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express-session": "^1.18.2",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.16",
"passport": "^0.7.0",
"passport-google-oauth20": "^2.0.0",
"prisma": "^5.20.0"
},
"name": "backend",

View File

@@ -0,0 +1,7 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "OAuth_AccessToken" TEXT,
ADD COLUMN "OAuth_RefreshToken" TEXT,
ADD COLUMN "auth_mode" TEXT NOT NULL DEFAULT 'password';
-- Update existing users to have password auth mode
UPDATE "User" SET "auth_mode" = 'password' WHERE "auth_mode" IS NULL;

View File

@@ -0,0 +1,8 @@
-- Make password optional for OAuth users
ALTER TABLE "User" ALTER COLUMN "password" DROP NOT NULL;
-- Add UUID generation extension if not exists
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Add default UUID generation for profileId
ALTER TABLE "User" ALTER COLUMN "profileId" SET DEFAULT uuid_generate_v4();

View File

@@ -0,0 +1,30 @@
-- Migration: Add Google OAuth Support
-- Description: Adds googleId field to User table and makes password optional for OAuth users
-- Date: 2025-01-01
-- Step 1: Add googleId column (nullable, unique)
ALTER TABLE "User" ADD COLUMN "googleId" TEXT;
-- Step 2: Make password column nullable (for OAuth users who don't have passwords)
ALTER TABLE "User" ALTER COLUMN "password" DROP NOT NULL;
-- Step 3: Add unique constraint on googleId
ALTER TABLE "User" ADD CONSTRAINT "User_googleId_key" UNIQUE ("googleId");
-- Step 4: Create index on googleId for faster lookups
CREATE INDEX "User_googleId_idx" ON "User"("googleId");
-- Step 5: Verify existing indexes (email should already be indexed)
-- CREATE INDEX "User_email_idx" ON "User"("email"); -- Should already exist
-- Notes:
-- 1. Existing users with passwords will continue to work normally
-- 2. New OAuth users will have NULL password and a googleId
-- 3. Users can have both password and googleId if they link accounts
-- 4. Email remains unique across all users (OAuth and traditional)
-- Rollback instructions (if needed):
-- ALTER TABLE "User" DROP CONSTRAINT "User_googleId_key";
-- DROP INDEX "User_googleId_idx";
-- ALTER TABLE "User" DROP COLUMN "googleId";
-- ALTER TABLE "User" ALTER COLUMN "password" SET NOT NULL;

View File

@@ -1,4 +1,3 @@
// Generator to create Prisma Client
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "darwin-arm64", "linux-musl-arm64-openssl-3.0.x"]
@@ -10,16 +9,71 @@ datasource db {
relationMode = "prisma"
}
enum Institute {
KJSIDS
SKSC
KJSCE
SIRC
KJSIM
SSA
KJSCEd
DLIS
MSSMPA
model Application {
applicationId String @id @default(uuid())
applicantId String
applicant User @relation("AppliedApplications", fields: [applicantId], references: [profileId])
institute Institute
department String
applicantName String
applicationType String
formData Json
formName String
resubmission Boolean @default(false)
facultyValidation ApplicationStatus?
hodValidation ApplicationStatus?
hoiValidation ApplicationStatus?
vcValidation ApplicationStatus?
accountsValidation ApplicationStatus?
rejectionFeedback String?
totalExpense Float @default(0)
proofOfTravel Bytes?
proofOfAccommodation Bytes?
proofOfAttendance Bytes?
expenseProof0 Bytes?
expenseProof1 Bytes?
expenseProof2 Bytes?
expenseProof3 Bytes?
expenseProof4 Bytes?
expenseProof5 Bytes?
expenseProof6 Bytes?
expenseProof7 Bytes?
expenseProof8 Bytes?
expenseProof9 Bytes?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
validators User[] @relation("ToValidateApplications")
@@index([applicantId])
@@index([createdAt])
}
model User {
profileId String @id @default(uuid())
userName String
email String @unique @db.Text
password String?
institute Institute?
department String?
designation Designation
appliedApplications Application[] @relation("AppliedApplications")
toValidateApplications Application[] @relation("ToValidateApplications")
OAuth_AccessToken String?
OAuth_RefreshToken String?
auth_mode String
@@index([email])
}
model ToValidateApplications {
A String
B String
@@unique([A, B], map: "_ToValidateApplications_AB_unique")
@@index([B], map: "_ToValidateApplications_B_index")
@@map("_ToValidateApplications")
}
enum ApplicationStatus {
@@ -37,65 +91,14 @@ enum Designation {
STUDENT
}
model User {
profileId String @id @default(uuid())
userName String
email String @unique @db.Text
password String
institute Institute?
department String?
designation Designation
appliedApplications Application[] @relation("AppliedApplications")
toValidateApplications Application[] @relation("ToValidateApplications")
@@index([email])
}
model Application {
applicationId String @id @default(uuid())
applicantId String
applicant User @relation("AppliedApplications", fields: [applicantId], references: [profileId])
institute Institute
department String
applicantName String
applicationType String
formData Json
formName String
resubmission Boolean @default(false)
facultyValidation ApplicationStatus?
hodValidation ApplicationStatus?
hoiValidation ApplicationStatus?
vcValidation ApplicationStatus?
accountsValidation ApplicationStatus?
rejectionFeedback String?
totalExpense Float @default(0)
proofOfTravel Bytes?
proofOfAccommodation Bytes?
proofOfAttendance Bytes?
expenseProof0 Bytes?
expenseProof1 Bytes?
expenseProof2 Bytes?
expenseProof3 Bytes?
expenseProof4 Bytes?
expenseProof5 Bytes?
expenseProof6 Bytes?
expenseProof7 Bytes?
expenseProof8 Bytes?
expenseProof9 Bytes?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
validators User[] @relation("ToValidateApplications")
@@index([applicantId])
@@index([createdAt])
enum Institute {
KJSIDS
SKSC
KJSCE
SIRC
KJSIM
SSA
KJSCEd
DLIS
MSSMPA
}

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;