Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 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
ec6b13b
feat: add certificate page and update navbar config for role-based ac…
codewkaushik404 Feb 20, 2026
b726fc8
feat: implement request page components including Card, Requests, and…
codewkaushik404 Feb 20, 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
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

cd frontend && npx lint-staged && cd ../backend && npx lint-staged
npx lint-staged
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
116 changes: 116 additions & 0 deletions backend/config/passportConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
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) {
//console.log("No email found in Google profile");
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 });
//console.log("Looking for existing user with email:", email, "Found:", !!user);

if (user) {
// If user exists, return the user
//console.log("Returning existing user:", user.username);
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,
});
//console.log("User is",newUser);
return done(null, newUser);
} catch (error) {
console.error("Error in Google strategy:", 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);
}

}));


//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) {
done(err, null);
}
});

module.exports = passport;
8 changes: 7 additions & 1 deletion backend/controllers/analyticsController.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
const {User, Achievement, UserSkill, Event, Position, PositionHolder,OrganizationalUnit}=require('../models/schema');
const mongoose = require("mongoose");
const getCurrentTenureRange = require('../utils/getTenureRange');

const User = require("../models/userSchema");
const Achievement = require("../models/achievementSchema");
const Position = require("../models/positionSchema");
const PositionHolder = require("../models/positionHolderSchema");
const OrganizationalUnit = require("../models/organizationSchema");
const Event = require("../models/eventSchema");
const { UserSkill } = require("../models/schema");

exports.getPresidentAnalytics= async (req,res) => {
try {
Expand Down
158 changes: 158 additions & 0 deletions backend/controllers/certificateController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@

const User = require("../models/userSchema");
const Position = require("../models/positionSchema");
const PositionHolder = require("../models/positionHolderSchema");
const OrganizationalUnit = require("../models/organizationSchema");
const { CertificateBatch } = require("../models/certificateSchema");
const { validateBatchSchema, zodObjectId } = require("../utils/batchValidate");

async function createBatch(req, res) {
//console.log(req.user);
try{
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 !== "CLUB_COORDINATOR") {
return res.status(403).json({ message: "Not authorized to perform the task" });
}

//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 on lines +44 to +48
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Null-pointer crash: position._id accessed before the null check.

Line 45 dereferences position._id via console.log, but the !position guard is on line 46. If Position.findById returns null, this line throws a TypeError.

🐛 Proposed fix
     const position = await Position.findById(positionHolder.position_id);
-    console.log(position._id);
     if (!position) {
       return res.status(403).json({ message: "Your position is invalid" });
     }
📝 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
const position = await Position.findById(positionHolder.position_id);
console.log(position._id);
if (!position) {
return res.status(403).json({ message: "Your position is invalid" });
}
const position = await Position.findById(positionHolder.position_id);
if (!position) {
return res.status(403).json({ message: "Your position is invalid" });
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/controllers/certificateController.js` around lines 44 - 48, The code
logs position._id before verifying Position.findById returned a value, which can
throw when position is null; in the certificate controller locate the call to
Position.findById(positionHolder.position_id) and move the null-check before any
access or logging of position._id (or use safe access like position?._id),
returning the existing 403 response when position is falsy and only then
logging/using position._id.


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}` });
if(!gensecObj){
return res.status(500).json({ message: "General Secretary not found" });
}
//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 (!presidentObj) {
return res.status(500).json({ message: "President not found" });
Comment on lines +105 to +114
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Null-pointer crashes: presidentHolder and presidentObj accessed before null checks.

  • Line 108: presidentHolder.user_id.toString() — if PositionHolder.findOne returns null, this throws.
  • Line 112: console.log(presidentObj._id) runs before the !presidentObj check on line 113.
🐛 Proposed fix
     const presidentHolder = await PositionHolder.findOne({
       position_id: presidentPosition._id,
     });
+    if (!presidentHolder) {
+      return res.status(500).json({ message: "President holder not found for council" });
+    }
     const presidentId = presidentHolder.user_id.toString();
-    //console.log(presidentId);
     const presidentObj = await User.findById(presidentId);
-
-    console.log(presidentObj._id);
     if (!presidentObj) {
       return res.status(500).json({ message: "President not found" });
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/controllers/certificateController.js` around lines 105 - 114, The
code accesses presidentHolder.user_id and presidentObj._id before verifying
those objects exist; update the logic around PositionHolder.findOne and
User.findById so you first check that presidentHolder is not null (e.g., after
calling PositionHolder.findOne) before using presidentHolder.user_id.toString(),
and only call User.findById if the holder exists; also move or remove the
console.log(presidentObj._id) so it occurs after you verify presidentObj is
non-null (and return the res.status(500).json({ message: "President not found"
}) immediately when either presidentHolder or presidentObj is missing). Ensure
you reference PositionHolder.findOne, presidentHolder, presidentHolder.user_id,
User.findById, presidentObj, and the res.status error return in your fix.

}

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 on lines +119 to +131
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

safeParse always returns an object — !validation is always false.

zodObjectId.safeParse(uid) returns { success: boolean, ... }, which is truthy regardless of validation outcome. The check on line 122 should be !validation.success.

🐛 Proposed fix
-        const validation = zodObjectId.safeParse(uid);
-        if (!validation) {
+        const validation = zodObjectId.safeParse(uid);
+        if (!validation.success) {
           return { uid, ok: false, reason: "Invalid ID" };
         }
📝 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
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 };
}),
);
const userChecks = await Promise.all(
users.map(async (uid) => {
const validation = zodObjectId.safeParse(uid);
if (!validation.success) {
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 };
}),
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/controllers/certificateController.js` around lines 119 - 131, The
validation logic in the users.map block is incorrect because
zodObjectId.safeParse(uid) returns an object with a success property, not a
falsy value on failure; update the check in the async mapper that currently does
`if (!validation)` to `if (!validation.success)` (and keep the rest of the flow
returning `{ uid, ok: false, reason: "Invalid ID" }`), referencing the
userChecks Promise.all, the users.map iterator, and zodObjectId.safeParse to
locate and fix the condition.


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 });
}catch(err){
res.status(500).json({message: err.message || "Internal server error"});
}
}

module.exports = {
createBatch,
};
17 changes: 7 additions & 10 deletions backend/controllers/dashboardController.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
// controllers/dashboardController.js
const {
Feedback,
Achievement,
UserSkill,
Skill,
Event,
PositionHolder,
Position,
OrganizationalUnit,
} = require("../models/schema");
const Feedback = require("../models/feedbackSchema");
const Achievement = require("../models/achievementSchema");
const Position = require("../models/positionSchema");
const PositionHolder = require("../models/positionHolderSchema");
const OrganizationalUnit = require("../models/organizationSchema");
const Event = require("../models/eventSchema");
const { UserSkill, Skill } = require("../models/schema");

const ROLES = {
PRESIDENT: "PRESIDENT",
Expand Down
9 changes: 6 additions & 3 deletions backend/controllers/eventControllers.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
const {Event} = require('../models/schema');
const Event = require('../models/eventSchema');

// fetch 4 most recently updated events
exports.getLatestEvents = async (req, res) => {
try{
const latestEvents = await Event.find({})
.sort({updated_at: -1})
.limit(4)
.select('title updated_at schedule.venue status');
.select('title updatedAt schedule.venue status');

if(!latestEvents){
return res.status(404).json({message: "No events are created"});
}
const formatedEvents =latestEvents.map(event=>({
id: event._id,
title: event.title,
date: event.updated_at.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
date: event.updatedAt?.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
venue: (event.schedule && event.schedule.venue) ? event.schedule.venue : 'TBA',
status: event.status || 'TBD'
}))
Expand Down
Loading