Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
cbefec9
chore(config): move db connection and passport strategy to config folder
codewkaushik404 Jan 29, 2026
78dbc24
fix(schema): update user schema to support correct login and registra…
codewkaushik404 Jan 29, 2026
f2e5883
refactor(auth): replace passport-local-mongoose with manual auth impl…
codewkaushik404 Jan 29, 2026
ae0295f
feat(validation): add zod validation for auth routes with IIT Bhilai …
codewkaushik404 Jan 29, 2026
57c4189
feat(auth): add manual JWT authentication middleware
codewkaushik404 Jan 30, 2026
61dfd89
refactor(schema): update certificate schema
codewkaushik404 Jan 30, 2026
29bc583
feat(certificates): implement controller logic to create certificate …
codewkaushik404 Jan 30, 2026
a8b4d8e
feat(certificates): implement controller logic to create certificate …
codewkaushik404 Jan 30, 2026
82d3b70
feat(validation): add Zod schema to validate certificate batch creati…
codewkaushik404 Jan 30, 2026
bde7d5e
Fix crashes and ensure intended behavior
codewkaushik404 Jan 30, 2026
ecc1ebd
refactor(auth): split schemas into separate files and fix local auth …
codewkaushik404 Feb 9, 2026
8126097
refactor(auth, models, middleware): refactor code to ensure robust l…
codewkaushik404 Feb 9, 2026
4e96a8e
Refactored authentication logic and fixed related bugs.
codewkaushik404 Feb 17, 2026
d3c0261
Refactored authentication logic and fixed related bugs. Switched to s…
codewkaushik404 Feb 17, 2026
2a31781
refactor few segments
codewkaushik404 Feb 17, 2026
53d7216
fix: api responses to handle frontend requirements
codewkaushik404 Feb 17, 2026
cdf07e2
refactor
codewkaushik404 Feb 17, 2026
c342d2b
fix: imports for models in controllers according to the updated struc…
codewkaushik404 Feb 17, 2026
649fb09
fix: imports for models in controllers according to the updated struc…
codewkaushik404 Feb 17, 2026
3fe6ed8
refactor
codewkaushik404 Feb 17, 2026
f521062
refactor: streamline authentication and registration processes, enhan…
codewkaushik404 Feb 18, 2026
2ef2e05
fix: incorrect imports for models in routes.
codewkaushik404 Feb 19, 2026
0f47b6a
refactor: improve auth flow
codewkaushik404 Feb 19, 2026
0bd1220
feat: add certificate page and update navbar config for role-based ac…
codewkaushik404 Feb 19, 2026
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
6 changes: 2 additions & 4 deletions backend/db.js → backend/config/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ dotenv.config();
const connectDB = async () => {
try {
const ConnectDB = process.env.MONGODB_URI;
await mongoose.connect(ConnectDB, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
//Removing the options as they are no longer needed from mongoose6+
await mongoose.connect(ConnectDB);
console.log("MongoDB Connected");
} catch (error) {
console.error("MongoDB Connection Error:", error);
Expand Down
113 changes: 113 additions & 0 deletions backend/config/passportConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const LocalStrategy = require("passport-local").Strategy;
const isIITBhilaiEmail = require("../utils/isIITBhilaiEmail");
const User = require("../models/userSchema");
const { loginValidate } = require("../utils/authValidate");
const bcrypt = require("bcrypt");
// Google OAuth Strategy
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: `${process.env.BACKEND_URL}/auth/google/verify`, // Update with your callback URL
},
async (accessToken, refreshToken, profile, done) => {
// Check if the user already exists in your database
const email = profile.emails?.[0]?.value;
if (!email) {
return done(null, false, { message: "Email not available from Google." });
}
if (!isIITBhilaiEmail(profile.emails[0].value)) {
console.log("Google OAuth blocked for: ", profile.emails[0].value);
return done(null, false, {
message: "Only @iitbhilai.ac.in emails are allowed.",
});
}
try {
const user = await User.findOne({ username: email });

if (user) {
// If user exists, return the user
return done(null, user);
}
// If user doesn't exist, create a new user in your database
const newUser = await User.create({
username: email,
role: "STUDENT",
strategy: "google",
personal_info: {
name: profile.displayName || "No Name",
email: email,
profilePic:
profile.photos && profile.photos.length > 0
? profile.photos[0].value
: "https://www.gravatar.com/avatar/?d=mp",
},
onboardingComplete: false,
});

return done(null, newUser);
} catch (error) {
return done(error);
}
},
),
);

//Local Strategy
passport.use(new LocalStrategy(async (username, password, done) => {

const result = loginValidate.safeParse({ username, password });

if (!result.success) {
let errors = result.error.issues.map((issue) => issue.message);
return done(null, false, {message: errors});
}

try{

const user = await User.findOne({ username });
if (!user) {
return done(null, false, {message: "Invalid user credentials"});
}


if (user.strategy !== "local" || !user.password) {
return done(null, false, { message: "Invalid login method" });
}

const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
return done(null, false, { message: "Invalid user credentials" });
}


return done(null, user);
}catch(err){
return done(err);
}

}));
Comment on lines +64 to +95

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat backend/utils/authValidate.js

Repository: OpenLake/Student_Database_COSA

Length of output: 548


LocalStrategy enforces IIT Bhilai email-only access; existing non-IIT users will be blocked.

The loginValidate schema in backend/utils/authValidate.js enforces a strict regex pattern (^[a-zA-Z0-9._%+-]+@iitbhilai\.ac\.in$) that rejects any username lacking the @iitbhilai.ac.in domain. This means any existing user account not using an IIT Bhilai email will be permanently locked out. Before deploying this change, audit the database for non-IIT Bhilai usernames and have a migration strategy ready (or disable this validation if it's unintended).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/config/passportConfig.js` around lines 59 - 92, The LocalStrategy in
passportConfig.js is rejecting non-@iitbhilai.ac.in usernames because
loginValidate (in backend/utils/authValidate.js) uses a strict domain regex;
update the validation or strategy to avoid locking out existing users: either
relax or change loginValidate to accept legacy usernames/emails (e.g., allow
plain usernames or any email) or modify the LocalStrategy to accept the raw
username first and only enforce the IIT Bhilai domain for new registrations
(e.g., perform domain check in the signup flow instead of in loginValidate), and
add a short DB audit/migration plan to handle existing non-IIT users; refer to
the LocalStrategy declaration (passport.use(new LocalStrategy(...))) and the
loginValidate schema to locate where to change validation and where to add
fallback logic.



//When login succeeds this will run
// serialize basically converts user obj into a format that can be transmitted(like a string, etc...)
// here take user obj and done callback and store only userId in session
passport.serializeUser((user, done) => {
done(null, user._id.toString());
});

//When a request comes in, take the stored id, fetch full user from DB, and attach it to req.user.
passport.deserializeUser(async (id, done) => {
try {
let user = await User.findById(id);
if(!user) return done(null, false);
done(null, user);
} catch (err) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
done(err, null);
}
});

module.exports = passport;
152 changes: 152 additions & 0 deletions backend/controllers/certificateController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
const {
User,
PositionHolder,
Position,
OrganizationalUnit,
} = require("../models/schema");
const { CertificateBatch } = require("../models/certificateSchema");
const { validateBatchSchema, zodObjectId } = require("../utils/batchValidate");

async function createBatch(req, res) {
//console.log(req.user);
const id = req.user.id;
const user = await User.findById(id);
if (!user) {
return res.status(404).json({ messge: "Invalid data (User not found)" });
}

if (user.role && user.role !== "CLUB_COORDINATOR") {
return res.status(403).json({ message: "Not authorized to perform the task" });
}
Comment thread
codewkaushik404 marked this conversation as resolved.
Outdated

//to get user club
// positionHolders({user_id: id}) -> positions({_id: position_id}) -> organizationalUnit({_id: unit_id}) -> unit_id = "Club name"
const { title, unit_id, commonData, template_id, users } = req.body;
const validation = validateBatchSchema.safeParse({
title,
unit_id,
commonData,
template_id,
users,
});

if (!validation.success) {
let errors = validation.error.issues.map(issue => issue.message);
return res.status(400).json({ message: errors });
}

// Get coordinator's position and unit
const positionHolder = await PositionHolder.findOne({ user_id: id });
if (!positionHolder) {
return res.status(403).json({ message: "You are not part of any position in a unit" });
}

const position = await Position.findById(positionHolder.position_id);
console.log(position._id);
if (!position) {
return res.status(403).json({ message: "Your position is invalid" });
}
Comment thread
codewkaushik404 marked this conversation as resolved.
Outdated

const userUnitId = position.unit_id.toString();
if (userUnitId !== unit_id) {
return res
.status(403)
.json({
message:
"You are not authorized to initiate batches outside of your club",
});
}

//const clubId = unit_id;
// Ensure unit_id is a Club
const unitObj = await OrganizationalUnit.findById(unit_id);
if (!unitObj || unitObj.type !== "Club") {
return res
.status(403)
.json({ message: "Invalid Data: unit is not a Club" });
}
console.log(unitObj._id);

// Get council (parent unit) and ensure it's a Council
if (!unitObj.parent_unit_id) {
return res
.status(403)
.json({ message: "Invalid Data: club does not belong to a council" });
}
console.log(unitObj.parent_unit_id);

const councilObj = await OrganizationalUnit.findById(unitObj.parent_unit_id);
if (!councilObj || councilObj.type !== "Council") {
return res.status(403).json({ message: "Invalid Data: council not found" });
}

//const councilId = councilObj._id.toString();
const presidentOrgUnitId = councilObj.parent_unit_id;
const category = councilObj.category.toUpperCase();

// Resolve General Secretary and President for the council (server-side, tamper-proof)
const gensecObj = await User.findOne({ role: `GENSEC_${category}` });
console.log(gensecObj._id);

const presidentPosition = await Position.findOne({
unit_id: presidentOrgUnitId,
title: /president/i,
});
if (!presidentPosition) {
return res
.status(500)
.json({ message: "President position not found for council" });
}
console.log(presidentPosition._id);

const presidentHolder = await PositionHolder.findOne({
position_id: presidentPosition._id,
});
const presidentId = presidentHolder.user_id.toString();
console.log(presidentId);
const presidentObj = await User.findById(presidentId);

console.log(presidentObj._id);
if (!gensecObj || !presidentObj) {
return res.status(500).json({ message: "Approvers not found" });
}
Comment thread
codewkaushik404 marked this conversation as resolved.
Outdated

const approverIds = [gensecObj._id.toString(), presidentId];

const userChecks = await Promise.all(
users.map(async (uid) => {
const validation = zodObjectId.safeParse(uid);
if (!validation) {
return { uid, ok: false, reason: "Invalid ID" };
}

const userObj = await User.findById(uid);
if (!userObj) return { uid, ok: false, reason: "User not found" };

return { uid, ok: true };
}),
);
Comment thread
codewkaushik404 marked this conversation as resolved.
Outdated

const invalidData = userChecks.filter((c) => !c.ok);
if (invalidData.length > 0) {
return res
.status(400)
.json({ message: "Invalid user data sent", details: invalidData });
}

const newBatch = await CertificateBatch.create({
title,
unit_id,
commonData,
templateId: template_id,
initiatedBy: id,
approverIds,
users,
});

res.json({ message: "New Batch created successfully", details: newBatch });
}
Comment thread
codewkaushik404 marked this conversation as resolved.

module.exports = {
createBatch,
};
38 changes: 27 additions & 11 deletions backend/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
const express = require("express");
require("dotenv").config();
// eslint-disable-next-line node/no-unpublished-require
const { connectDB } = require("./config/db.js");
const MongoStore = require("connect-mongo");
const cookieParser = require("cookie-parser");
const cors = require("cors");
const routes_auth = require("./routes/auth");
const routes_general = require("./routes/route");
const session = require("express-session");
const bodyParser = require("body-parser");
const { connectDB } = require("./db");
const myPassport = require("./models/passportConfig"); // Adjust the path accordingly
const myPassport = require("./config/passportConfig.js"); // Adjust the path accordingly
const onboardingRoutes = require("./routes/onboarding.js");
const profileRoutes = require("./routes/profile.js");
const feedbackRoutes = require("./routes/feedbackRoutes.js");
Expand All @@ -18,8 +19,8 @@ const positionsRoutes = require("./routes/positionRoutes.js");
const organizationalUnitRoutes = require("./routes/orgUnit.js");
const announcementRoutes = require("./routes/announcements.js");
const dashboardRoutes = require("./routes/dashboard.js");

const analyticsRoutes = require("./routes/analytics.js");
const certificateRoutes = require("./routes/certificateRoutes.js");
const app = express();

if (process.env.NODE_ENV === "production") {
Expand All @@ -28,23 +29,32 @@ if (process.env.NODE_ENV === "production") {

app.use(cors({ origin: process.env.FRONTEND_URL, credentials: true }));

// Connect to MongoDB
connectDB();

app.use(bodyParser.json());
app.use(cookieParser());

//Replaced bodyParser with express.json() - the new standard
app.use(express.json());

app.use(
session({
secret: "keyboard cat",
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === "production", // HTTPS only in prod
sameSite: process.env.NODE_ENV === "production" ? "none" : "lax", // cross-origin in prod
maxAge: 60*60*1000
},
store: MongoStore.create({
mongoUrl: process.env.MONGODB_URI,
ttl: 60*60*1000,
collectionName: "sessions"
}),
Comment on lines +48 to +52

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n backend/index.js | sed -n '40,60p'

Repository: OpenLake/Student_Database_COSA

Length of output: 825


🌐 Web query:

connect-mongo ttl parameter unit seconds documentation

💡 Result:

In connect-mongo, the ttl option is specified in seconds (default 1209600, i.e., 14 days). The docs describe it as “maximum lifetime (in seconds)” and examples set it as 14 * 24 * 60 * 60. [1], [2]


ttl is in seconds but is set to a millisecond value — sessions will persist for ~41.7 days.

The connect-mongo ttl parameter expects seconds. With ttl: 60*60*1000 (3,600,000 seconds), sessions persist for approximately 41.7 days in MongoDB, while the cookie's maxAge is 3,600,000 milliseconds (1 hour). This mismatch causes expired session cookies to remain in the database, wasting storage and keeping stale session data accessible.

Proposed fix
     store: MongoStore.create({
       mongoUrl: process.env.MONGODB_URI,
-      ttl: 60*60*1000,
+      ttl: 60 * 60, // 3600 seconds = 1 hour (matches cookie maxAge)
       collectionName: "sessions"
     }),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
store: MongoStore.create({
mongoUrl: process.env.MONGODB_URI,
ttl: 60*60*1000,
collectionName: "sessions"
}),
store: MongoStore.create({
mongoUrl: process.env.MONGODB_URI,
ttl: 60 * 60, // 3600 seconds = 1 hour (matches cookie maxAge)
collectionName: "sessions"
}),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/index.js` around lines 48 - 52, The ttl passed to MongoStore.create
is using milliseconds (60*60*1000) but connect-mongo expects seconds, causing
sessions to live ~41 days; update the ttl to be in seconds (e.g., 60*60) or
compute it from the session cookie maxAge (Math.floor(session.cookie.maxAge /
1000) or use the same constant) so ttl and cookie maxAge match; locate the
MongoStore.create call and replace the ttl expression accordingly to ensure
sessions expire from the DB when the cookie does.

name: "token"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's check the structure of the backend directory
echo "=== Backend directory structure ==="
find backend -type f -name "*.js" | head -20

Repository: OpenLake/Student_Database_COSA

Length of output: 797


🏁 Script executed:

#!/bin/bash
# Run the provided verification script
rg -n "cookies\|token\|req\.cookies" backend/middlewares/isAuthenticated.js -A2 -B2

Repository: OpenLake/Student_Database_COSA

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Check the context around line 53 in backend/index.js
echo "=== Context around line 53 in backend/index.js ==="
sed -n '40,70p' backend/index.js

Repository: OpenLake/Student_Database_COSA

Length of output: 1025


🏁 Script executed:

#!/bin/bash
# Search for where jwtIsAuthenticated is used
echo "=== Usage of jwtIsAuthenticated ==="
rg "jwtIsAuthenticated" backend -n -A1 -B1

Repository: OpenLake/Student_Database_COSA

Length of output: 789


🏁 Script executed:

#!/bin/bash
# Read the entire isAuthenticated.js file to see the implementation
cat -n backend/middlewares/isAuthenticated.js

Repository: OpenLake/Student_Database_COSA

Length of output: 2903


🏁 Script executed:

#!/bin/bash
# Check the certificate routes to understand the flow
cat -n backend/routes/certificateRoutes.js

Repository: OpenLake/Student_Database_COSA

Length of output: 399


Session cookie named "token" will conflict with JWT-based middleware.

The session middleware is configured with name: "token", which means it sets a cookie called "token" containing a session ID. However, jwtIsAuthenticated in the certificate routes reads req.cookies.token (line 24 of backend/middlewares/isAuthenticated.js) and attempts to verify it as a JWT. When jwt.verify() is called on the session ID string, verification will fail, causing all certificate endpoints to return a 401 Unauthorized error.

Rename the session cookie to avoid the collision:

🐛 Proposed fix
-    name: "token"
+    name: "sid"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
name: "token"
name: "sid"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/index.js` at line 53, The session middleware in backend/index.js sets
the cookie name to "token", which collides with jwtIsAuthenticated in
backend/middlewares/isAuthenticated.js that expects req.cookies.token to be a
JWT; rename the session cookie (e.g., to "sid" or "sessionId") in the session
configuration (the object containing name: "token") and update any server code
that reads that cookie name accordingly so jwtIsAuthenticated continues to read
req.cookies.token for JWTs while the session middleware writes the new cookie
name; ensure no other logic assumes the old "token" cookie name.

}),
);

//Needed to initialize passport and all helper methods to req object
app.use(myPassport.initialize());
app.use(myPassport.session());

Expand All @@ -67,8 +77,14 @@ app.use("/api/announcements", announcementRoutes);
app.use("/api/dashboard", dashboardRoutes);
app.use("/api/announcements", announcementRoutes);
app.use("/api/analytics", analyticsRoutes);
app.use("/api/certificate-batches", certificateRoutes);

// Start the server
app.listen(process.env.PORT || 8000, () => {
console.log(`connected to port ${process.env.PORT || 8000}`);
});

(async function(){
// Connect to MongoDB
await connectDB();
app.listen(process.env.PORT || 5000, () => {
console.log(`connected to port ${process.env.PORT || 5000}`);
});
})();
Loading