Skip to content

Conversation

@codewkaushik404
Copy link

@codewkaushik404 codewkaushik404 commented Jan 29, 2026


name: "Pull Request"
about: Propose and submit changes to the project for review
title: "PR: Refactor login/register and add Zod validation"
labels: ""
assignees: harshitap1305, sakshi1755

Changes Introduced

  • Moved: DB connection and Passport strategy to config folder for cleaner project structure
  • Modified: User schema to ensure login & register routes work as intended
  • Implemented: Manual login and registration logic (replacing broken User.register() and plugins)
  • Added: Zod validation for input fields during POST requests
    • Integrated email regex check for "iitbhilai.ac.in" directly in Zod
    • Validation logic moved to utils folder for modularity

Why This Change?

  • Problem:
    • Previous Passport User.register() implementation was broken
    • Input validation and email checks were scattered and inconsistent
  • Solution:
    • Refactored authentication flow manually for reliability
    • Centralized configuration and validation logic
  • Impact:
    • Ensures only valid IIT Bhilai emails can register
    • Makes login/register routes maintainable and error-free

Screenshots

image

Testing

  • Ran unit tests and all passed (npm test)
  • Manually tested login and registration with valid and invalid inputs
    • Test Case 1: Login with correct credentials → Success
    • Test Case 2: Login with wrong credentials → Fails as expected
    • Test Case 3: Register with non-IITB email → Fails
    • Test Case 4: Register with valid IITB email → Success
  • Verified no new console warnings or errors

Documentation Updates

  • Updated the README.md with new instructions
  • Added clear code comments where logic is complex
  • N/A

Checklist

  • Created a new branch for this PR (refactor/login-register)
  • Starred the repository
  • Code follows project coding style and conventions
  • Commit messages are clear and follow guidelines
  • Performed a self-review of the code
  • Added tests that prove fixes/features work
  • All new and existing tests passed locally
  • No breaking changes introduced

Deployment Notes

  • Requires database migration/schema update
  • Requires new environment variables
  • N/A

Additional Notes

  • Refactored authentication flow to be independent of broken Passport plugins
  • Centralized configuration and validation improves maintainability

Summary by CodeRabbit

  • New Features
    • Google OAuth sign‑in and improved username/password sign‑in with short-lived JWTs
    • Achievement, Event, Feedback, Organization, Position and Position‑Holder support (create/view/manage)
    • Enhanced user profiles with onboarding, timestamps and secure password handling
  • Stability & Validation
    • Server‑side input validation for registration/login and reliable DB connection handling

@vercel
Copy link

vercel bot commented Jan 29, 2026

@codewkaushik404 is attempting to deploy a commit to the openlake's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 29, 2026

Walkthrough

This PR refactors authentication from Passport LocalStrategy to JWT tokens with bcrypt, introduces Google OAuth with IIT Bhilai email domain validation, reorganizes monolithic schema into separate model files, adds certificate batch management with multi-level approval workflows, and updates route imports for consistency.

Changes

Cohort / File(s) Summary
Database & Configuration
backend/config/db.js, backend/config/passportConfig.js
MongoDB connection initialization via mongoose with connectDB/closeDB utilities; Google OAuth strategy with email domain validation and automatic user creation.
Schema Reorganization - New Individual Models
backend/models/userSchema.js, backend/models/achievementSchema.js, backend/models/eventSchema.js, backend/models/feedbackSchema.js, backend/models/organizationSchema.js, backend/models/positionHolderSchema.js, backend/models/positionSchema.js
Extracted individual Mongoose schema definitions from monolithic schema file; User model includes bcrypt pre-save password hashing, Google/local strategy support, and nested personal/academic/contact info.
Schema Reorganization - Consolidation
backend/models/schema.js, backend/models/passportConfig.js
Removed User, Feedback, Achievement, Event, Position, PositionHolder, OrganizationalUnit exports; retained only UserSkill, Skill, Announcement; removed passport-local-mongoose and mongoose-findorcreate plugins; updated User import path.
Certificate Management
backend/models/certificateSchema.js, backend/controllers/certificateController.js, backend/routes/certificateRoutes.js
New CertificateBatch and Certificate schemas with multi-level approval workflow (PendingL1/PendingL2 states); controller validates batch creation, enforces role-based authorization, resolves approvers (General Secretary/President), and validates user participation.
Authentication Refactor
backend/routes/auth.js, backend/middlewares/isAuthenticated.js
Replaced Passport LocalStrategy with custom JWT-based authentication; manual password hashing/verification via bcrypt; added jwtIsAuthenticated middleware for token verification; Google OAuth routes retained with updated User model references.
Route Import Updates
backend/routes/achievements.js, backend/routes/analytics.js, backend/routes/announcements.js, backend/routes/dashboard.js, backend/routes/events.js, backend/routes/feedbackRoutes.js, backend/routes/onboarding.js, backend/routes/orgUnit.js, backend/routes/positionRoutes.js, backend/routes/profile.js, backend/routes/skillsRoutes.js
Updated isAuthenticated imports from default to named exports; adjusted Achievement import from schema.js to achievementSchema.js; standardized string quotes and formatting.
Utilities
backend/utils/authValidate.js, backend/utils/batchValidate.js
New Zod validators for login/registration (IIT Bhilai email domain + password constraints) and batch creation (title, unit_id, template_id, users with ObjectId validation).
Server Setup & Dependencies
backend/index.js, backend/package.json, package.json
Added connectDB invocation, cookie-parser middleware, and certificate routes at /api/certificate-batches; replaced body-parser with express.json(); updated passport import path; added bcrypt, cookie-parser, zod dependencies; removed passport-local packages.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server as Auth Handler
    participant UserDB as User DB
    participant BCrypt as BCrypt
    participant JWT as JWT Signer

    Client->>Server: POST /login (username, password)
    Server->>Server: Validate input with loginValidate
    alt Invalid Input
        Server-->>Client: 400 Validation Error
    end
    Server->>UserDB: Query user by username
    alt User Not Found
        UserDB-->>Server: null
        Server-->>Client: 401 Invalid credentials
    end
    UserDB-->>Server: User document
    Server->>BCrypt: Verify password hash
    alt Password Mismatch
        BCrypt-->>Server: false
        Server-->>Client: 401 Invalid credentials
    end
    BCrypt-->>Server: true
    Server->>JWT: Sign token (user_id, 30 min expiry)
    JWT-->>Server: JWT token
    Server->>Server: Set httpOnly cookie
    Server-->>Client: 200 Login success + cookie
Loading
sequenceDiagram
    participant Client
    participant GoogleOAuth as Google OAuth
    participant Server as Callback Handler
    participant UserDB as User DB

    Client->>GoogleOAuth: Request Google auth
    GoogleOAuth-->>Client: Redirect to Google login
    Client->>GoogleOAuth: Authenticate
    GoogleOAuth-->>Server: Callback with profile
    Server->>Server: Validate IIT Bhilai email domain
    alt Non-IIT Email
        Server-->>Client: 403 Invalid domain
    end
    Server->>UserDB: Query user by email
    alt User Exists
        UserDB-->>Server: User document
    else User Not Found
        Server->>UserDB: Create new user (STUDENT role, google strategy)
        UserDB-->>Server: New user document
    end
    Server->>Server: Serialize user
    Server-->>Client: 200 Auth success
Loading
sequenceDiagram
    participant Client
    participant CertController as Certificate Controller
    participant UserDB as User DB
    participant OrgDB as Organization DB
    participant ApproverDB as Approver Lookup
    participant CertDB as Certificate DB

    Client->>CertController: POST /api/certificate-batches (batch data)
    CertController->>CertController: Validate with jwtIsAuthenticated
    CertController->>UserDB: Fetch initiator user
    alt User Not Found
        UserDB-->>CertController: null
        CertController-->>Client: 404 User not found
    end
    CertController->>CertController: Check role = CLUB_COORDINATOR
    alt Unauthorized
        CertController-->>Client: 403 Forbidden
    end
    CertController->>CertController: Validate batch schema (title, unit_id, users)
    alt Validation Fails
        CertController-->>Client: 400 Validation errors
    end
    CertController->>OrgDB: Fetch organization unit (club)
    CertController->>CertController: Verify coordinator in unit
    alt Not coordinator of club
        CertController-->>Client: 403 Forbidden
    end
    CertController->>OrgDB: Validate parent (Council)
    CertController->>ApproverDB: Resolve General Secretary
    CertController->>ApproverDB: Resolve President
    alt Approvers Not Found
        ApproverDB-->>CertController: null
        CertController-->>Client: 500 Approvers not found
    end
    CertController->>CertController: Validate each user ID
    alt Invalid user ID
        CertController-->>Client: 400 Invalid user
    end
    CertController->>CertDB: Create CertificateBatch
    CertDB-->>CertController: Batch created
    CertController-->>Client: 200 Batch created with details
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 Hop hop, the auth now flows with tokens so bright,
JWT and bcrypt replacing old Passport's light,
Schemas split clean like carrots in spring,
Certificates batch with approvals to bring,
Google's domain guard says "Welcome home, friend!"

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title 'Refactor/login register' is vague and lacks specificity about the actual changes made. Use a more descriptive title such as 'Refactor authentication with manual login/register and Zod validation' to clearly convey the main changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The PR description is comprehensive and well-structured, covering all key aspects including changes, motivation, testing, and deployment notes as per template.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/routes/auth.js (1)

208-226: user.setPassword() does not exist on the User model and will throw at runtime.

The User schema does not have the passport-local-mongoose plugin attached, so the setPassword method is unavailable. Line 218 will crash with "user.setPassword is not a function" when a password reset is attempted. The inconsistency is problematic—register and login both use bcrypt, but reset-password relies on a non-existent method.

Use bcrypt.hash() to hash the password, consistent with the register route pattern:

Suggested fix
   try {
     jwt.verify(token, secret);
-    user.setPassword(password, async (error) => {
-      if (error) {
-        return res.status(500).json({ message: "Error resetting password" });
-      }
-      await user.save();
-      return res
-        .status(200)
-        .json({ message: "Password has been reset successfully" });
-    });
+    const hashed = await bcrypt.hash(password, Number(process.env.SALT));
+    user.password = hashed;
+    await user.save();
+    return res
+      .status(200)
+      .json({ message: "Password has been reset successfully" });
   } catch (error) {
     console.log(error);
     return res.status(400).json({ message: "Invalid or expired token" });
   }
🤖 Fix all issues with AI agents
In `@backend/config/passportConfig.js`:
- Around line 1-2: The import for GoogleStrategy is wrong: change the require
for GoogleStrategy so it references the Strategy property from the
passport-google-oauth20 package (so that new GoogleStrategy(...) is a valid
constructor); locate the GoogleStrategy import statement (currently const
GoogleStrategy = require("passport-google-oauth20")) and update it to pull
.Strategy to avoid the "not a constructor" runtime error when instantiating
GoogleStrategy in passport configuration.
- Around line 54-62: Change passport.serializeUser to store only the user's
unique identifier (e.g., user._id) instead of the whole user object, and adjust
passport.deserializeUser to accept that id (e.g., userId) and call
User.findById(userId) to load the full user; update references to
passport.serializeUser, passport.deserializeUser, and User.findById (remove
reliance on userKey._id) so sessions contain only the ID and deserialization
fetches the user record.
- Around line 14-21: The verify callback for the Google Passport strategy
accesses profile.emails[0].value without checking for existence; add a defensive
guard similar to the profile.photos check: ensure profile.emails is an array and
has at least one entry before reading .value (e.g., if (!profile.emails ||
profile.emails.length === 0) return done(null, false, { message: "Google profile
has no email" })), then use profile.emails[0].value for isIITBhilaiEmail and
logging; update all usages in the async (accessToken, refreshToken, profile,
done) => callback to avoid crashes when emails are missing.

In `@backend/routes/auth.js`:
- Around line 70-101: Remove trusting client-supplied role: stop extracting role
from req.body and do not pass it into User.create; instead determine role
server-side (e.g., set a default like "user" or map/whitelist based on
authenticated context) before creating the user. Update the destructuring that
currently includes role (from req.body) and replace the role passed to
User.create with the server-controlled value (not the request value) and, if you
need role assignment rules, implement a small whitelist/mapping in the route
handler to derive role securely.
- Around line 44-52: Fix the response syntax and guard against non-local
accounts before comparing passwords: replace the incorrect
res.json(401).json(...) calls with res.status(401).json(...), and before calling
bcrypt.compare(password, user.password) (after User.findOne({ username })),
ensure the account is a local/password account—e.g., check for a local strategy
flag (user.authStrategy === 'local') or at minimum that user.password exists; if
not, return res.status(401).json({ message: "Invalid user credentials" })
instead of attempting bcrypt.compare. Use the existing User.findOne, username,
bcrypt.compare and res.status symbols to locate where to add the guard and
change the response call.
- Around line 44-52: The error responses in the authentication flow use
res.json(401).json(...) which incorrectly sets a 200 status; update each
occurrence to use res.status(401).json(...) instead — specifically change the
early-return after User.findOne (the block using User.findOne({ username }) and
returning on missing user) and the password check block using
bcrypt.compare(password, user.password), plus any other similar occurrences
around the authentication routes in auth.js, so the responses call
res.status(401).json({ message: "Invalid user credentials" }) to correctly set
the HTTP 401 status.
- Around line 95-107: The registration response returns the created Mongoose
document `newUser` which includes the hashed `password`; before calling
`res.json` remove or omit the password field (e.g., convert `newUser` to a plain
object and delete the `password` property or re-query with a projection) so that
`res.json({ message: "Registered Successfully", user: ... })` never contains the
hashed password; update the code around `User.create`/`newUser` and the
`res.json` call to return the sanitized user object instead.

In `@backend/utils/validate.js`:
- Line 1: The module backend/utils/validate.js requires the zod package (const
zod = require("zod")), but zod is missing from backend/package.json; add "zod"
to the dependencies in backend/package.json (e.g., use npm install --save zod or
add the dependency entry) and run the install so the require("zod") in
validate.js resolves at runtime.
🧹 Nitpick comments (3)
backend/utils/validate.js (1)

3-14: DRY the IIT Bhilai email regex to avoid drift.

Lines 3-13 duplicate the same regex (and another check exists in backend/utils/isIITBhilaiEmail). Extract a shared constant/helper so all auth entry points stay consistent.

♻️ Proposed refactor
+const IITB_EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@iitbhilai\.ac\.in$/i;
+
 const loginValidate = zod.object({
-  username: zod.string().regex(/^[a-zA-Z0-9._%+-]+@iitbhilai\.ac\.in$/i),
+  username: zod.string().regex(IITB_EMAIL_REGEX),
   password: zod.string().min(8),
 });

 const registerValidate = zod.object({
-  username: zod.string().regex(/^[a-zA-Z0-9._%+-]+@iitbhilai\.ac\.in$/i),
+  username: zod.string().regex(IITB_EMAIL_REGEX),
   password: zod.string().min(8),
   user_id: zod.string().min(2),
   name: zod.string().min(5),
   role: zod.string().min(5),
 });
backend/config/db.js (1)

17-25: Return the close promise so shutdown can await completion.

Line 17 doesn’t return/await the close operation, so callers can’t reliably wait for cleanup.

♻️ Suggested refactor
-const closeDB = () => {
-  mongoose.connection
-    .close()
-    .then(() => {
-      console.log("MongoDB connection closed");
-    })
-    .catch((err) => {
-      console.error("Error while closing MongoDB connection:", err);
-    });
-};
+const closeDB = async () => {
+  try {
+    await mongoose.connection.close();
+    console.log("MongoDB connection closed");
+  } catch (err) {
+    console.error("Error while closing MongoDB connection:", err);
+  }
+};
backend/models/schema.js (1)

22-28: Consider adding select: false to prevent accidental password hash exposure.

The minLength: 8 option is correctly spelled and fully supported in Mongoose v7.6.8 (both minLength and minlength are valid). However, add select: false to ensure password hashes are not returned in default queries.

🛠️ Suggested update
    password: {
      type: String,
      required: function () {
        return this.strategy === "local";
      },
      minLength: 8,
+     select: false,
    },

Comment on lines +14 to +21
async (accessToken, refreshToken, profile, done) => {
// Check if the user already exists in your database
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.",
});
}
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 | 🟠 Major

🧩 Analysis chain

🌐 Web query:

passport-google-oauth20 profile.emails availability scopes required

💡 Result:

In passport-google-oauth20, profile.emails is not guaranteed to be present unless you request an email-related scope.

  • To get profile.emails, request at least the email scope (commonly alongside profile):

    passport.authenticate('google', { scope: ['profile', 'email'] })

    Google documents email as “See your primary Google Account email address.” [1]

  • In Passport’s normalized profile, emails (when provided by the provider) are exposed as an array profile.emails, with items like { value: 'user@example.com' } (so you typically read profile.emails[0].value). [2]

  • If you don’t include email (or an equivalent like https://www.googleapis.com/auth/userinfo.email), profile.emails may be undefined. [1][3]

Sources: Google OAuth/OpenID scopes [1], Passport normalized profile fields [2], Passport scope usage reference [3].


🌐 Web query:

Google OAuth strategy profile emails missing undefined passport

💡 Result:

This usually happens for one of these Passport/Google OAuth gotchas:

  1. You’re not actually requesting the email scope at login time (it gets overridden).
    Even if you set scope: ['email','profile'] in the strategy, a later passport.authenticate('google', { scope: ... }) on your route overrides it. If that route only asks for profile, Google won’t return emails, so profile.emails is undefined. Fix by ensuring the auth route includes email too. (stackoverflow.com)
app.get('/auth/google',
  passport.authenticate('google', { scope: ['profile', 'email'] })
);
  1. You’re using the wrong strategy/package (or wrong verify callback signature), so profile isn’t what you think.
    A common case is importing GoogleStrategy from passport-google-oidc but using a passport-google-oauth*-style callback like (accessToken, refreshToken, profile, done). Then profile can look like a string/undefined and profile.emails blows up. Use the correct verify signature for the package you installed, or switch to passport-google-oauth20 if you want the OAuth2-style profile. (stackoverflow.com)

  2. Use the full userinfo scopes if needed.
    If ['profile','email'] still doesn’t populate emails in your setup, request the explicit userinfo scopes. (stackoverflow.com)

scope: [
  'https://www.googleapis.com/auth/userinfo.profile',
  'https://www.googleapis.com/auth/userinfo.email',
]

If you paste your passport.use(...) and your /auth/google route, I can point to the exact line where the scope/signature is going wrong.

Citations:


🏁 Script executed:

# Check if there are any other references to profile.emails in the codebase
rg "profile\.emails" --type js

Repository: OpenLake/Student_Database_COSA

Length of output: 1005


🏁 Script executed:

# Get the full content of backend/config/passportConfig.js to see scope initialization
cat -n backend/config/passportConfig.js | head -60

Repository: OpenLake/Student_Database_COSA

Length of output: 2438


🏁 Script executed:

# Also check backend/models/passportConfig.js to see if there's a pattern
cat -n backend/models/passportConfig.js | head -60

Repository: OpenLake/Student_Database_COSA

Length of output: 2424


🏁 Script executed:

# Check if there's any route that calls passport.authenticate with Google strategy
# Look for the /auth/google route or similar
rg "passport\.authenticate.*google" -A 2 -B 2 --type js

Repository: OpenLake/Student_Database_COSA

Length of output: 550


🏁 Script executed:

# Also check if there's any scope configuration elsewhere
rg "scope.*email" --type js

Repository: OpenLake/Student_Database_COSA

Length of output: 164


Add defensive check for missing Google profile emails.

Although the /auth/google route correctly requests the email scope, the code accesses profile.emails[0].value on lines 16, 17, 23, 31, and 36 without checking if the emails array exists. If the OAuth configuration is misconfigured or Google doesn't populate emails, this causes a crash. The code already guards profile.photos (line 38)—apply the same defensive pattern to emails:

🛡️ Suggested guard
     async (accessToken, refreshToken, profile, done) => {
       // Check if the user already exists in your database
-      if (!isIITBhilaiEmail(profile.emails[0].value)) {
+      const email = profile.emails?.[0]?.value;
+      if (!email) {
+        return done(null, false, { message: "Email not available from Google." });
+      }
+      if (!isIITBhilaiEmail(email)) {
-        console.log("Google OAuth blocked for: ", profile.emails[0].value);
+        console.log("Google OAuth blocked for: ", email);
         return done(null, false, {
           message: "Only `@iitbhilai.ac.in` emails are allowed.",
         });
       }
       try {
-        const user = await User.findOne({ username: profile.emails[0].value });
+        const user = await User.findOne({ username: email });
           ...
-          username: profile.emails[0].value,
+          username: email,
           ...
-            email: profile.emails[0].value,
+            email: email,
🤖 Prompt for AI Agents
In `@backend/config/passportConfig.js` around lines 14 - 21, The verify callback
for the Google Passport strategy accesses profile.emails[0].value without
checking for existence; add a defensive guard similar to the profile.photos
check: ensure profile.emails is an array and has at least one entry before
reading .value (e.g., if (!profile.emails || profile.emails.length === 0) return
done(null, false, { message: "Google profile has no email" })), then use
profile.emails[0].value for isIITBhilaiEmail and logging; update all usages in
the async (accessToken, refreshToken, profile, done) => callback to avoid
crashes when emails are missing.

Comment on lines +54 to +62
passport.serializeUser((user, done) => {
done(null, user);
});

passport.deserializeUser(async (userKey, done) => {
try {
let user = await User.findById(userKey._id);
done(null, user);
} catch (err) {
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Locate and examine the passport config file
find . -type f -name "passportConfig.js" | head -5

Repository: OpenLake/Student_Database_COSA

Length of output: 143


🌐 Web query:

passport.js serializeUser deserializeUser best practices store user id

💡 Result:

Best practice: serialize the minimum stable identifier (usually the database user id) into the session, and in deserializeUser load the user (or a safe subset) from your data store.

What to store in serializeUser

  • Common/default: store only user.id to keep the session small, and query the DB on requests. (passportjs.org)
  • Passport’s recommended tradeoff: store only what you need on every request (e.g., id plus username/photo if shown on every page) to reduce repeated DB queries. (passportjs.org)
  • Avoid storing the whole user object: it duplicates mutable/sensitive data and can become stale (e.g., username/permissions changed but session still has old values). (stackoverflow.com)

What to do in deserializeUser

  • Treat serializeUser and deserializeUser as inverses: serializeUser(user) -> key, deserializeUser(key) -> user. (stackoverflow.com)
  • deserializeUser runs on subsequent requests (via passport.session()), attaches the result to req.user. (stackoverflow.com)
  • Handle errors and “user not found” cleanly (e.g., return false if the id no longer exists). (stackoverflow.com)

Typical pattern (ID-only session)

passport.serializeUser((user, done) => done(null, user.id));

passport.deserializeUser(async (id, done) => {
  try {
    const user = await User.findById(id);   // load from DB
    if (!user) return done(null, false);
    return done(null, user);
  } catch (err) {
    return done(err);
  }
});

This matches Passport’s “minimize session data, query as needed” example. (passportjs.org)

Citations:


🏁 Script executed:

cat -n backend/config/passportConfig.js | sed -n '50,65p'

Repository: OpenLake/Student_Database_COSA

Length of output: 439


Serialize only the user ID instead of the entire user object.

Lines 54–61 serialize the full user object into the session, which unnecessarily stores sensitive data and bloats sessions. Per Passport.js best practices, serialize only the user ID and fetch the full user on subsequent requests in deserializeUser.

🛠️ Suggested change
 passport.serializeUser((user, done) => {
-  done(null, user);
+  done(null, user._id.toString());
 });

 passport.deserializeUser(async (userKey, done) => {
   try {
-    let user = await User.findById(userKey._id);
+    let user = await User.findById(userKey);
     done(null, user);
   } catch (err) {
     done(err);
   }
 });
📝 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
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser(async (userKey, done) => {
try {
let user = await User.findById(userKey._id);
done(null, user);
} catch (err) {
passport.serializeUser((user, done) => {
done(null, user._id.toString());
});
passport.deserializeUser(async (userKey, done) => {
try {
let user = await User.findById(userKey);
done(null, user);
} catch (err) {
done(err);
}
});
🤖 Prompt for AI Agents
In `@backend/config/passportConfig.js` around lines 54 - 62, Change
passport.serializeUser to store only the user's unique identifier (e.g.,
user._id) instead of the whole user object, and adjust passport.deserializeUser
to accept that id (e.g., userId) and call User.findById(userId) to load the full
user; update references to passport.serializeUser, passport.deserializeUser, and
User.findById (remove reliance on userKey._id) so sessions contain only the ID
and deserialization fetches the user record.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
backend/models/passportConfig.js (3)

31-43: ⚠️ Potential issue | 🔴 Critical

role: "STUDENT" is set but the User schema has no role field — this value will be silently dropped.

The userSchema.js model does not define a role field. With Mongoose's default strict mode, this property will be discarded on save. If roles are needed for authorization, add the field to the schema.

#!/bin/bash
# Verify that the User schema does not have a role field
rg -n "role" backend/models/userSchema.js

55-57: ⚠️ Potential issue | 🟠 Major

Serializing the entire user object into the session is inefficient and leaks the password hash.

serializeUser should store only the user ID. The full document (including the hashed password) ends up in the session store.

Proposed fix
 passport.serializeUser((user, done) => {
-  done(null, user);
+  done(null, user._id);
 });
 
-passport.deserializeUser(async (userKey, done) => {
+passport.deserializeUser(async (id, done) => {
   try {
-    let user = await User.findById(userKey._id);
+    let user = await User.findById(id);
     done(null, user);
   } catch (err) {
     done(err);
   }
 });

17-18: ⚠️ Potential issue | 🟡 Minor

Logging user email on OAuth rejection is a PII/compliance concern.

Consider logging a sanitized identifier or omitting the email entirely.

Proposed fix
-        console.log("Google OAuth blocked for: ", profile.emails[0].value);
+        console.log("Google OAuth blocked: non-institutional email domain");
backend/routes/auth.js (1)

224-247: ⚠️ Potential issue | 🔴 Critical

user.setPassword() will fail at runtime — passport-local-mongoose was removed.

The /reset-password POST handler at line 234 calls user.setPassword(password, callback), which is a passport-local-mongoose plugin method that no longer exists. This will throw a TypeError: user.setPassword is not a function at runtime.

Replace this with direct password assignment and save, relying on the pre-save hook at backend/models/userSchema.js:97-100 to handle bcrypt hashing:

Proposed fix
   try {
     jwt.verify(token, secret);
-    user.setPassword(password, async (error) => {
-      if (error) {
-        return res.status(500).json({ message: "Error resetting password" });
-      }
-      await user.save();
-      return res
-        .status(200)
-        .json({ message: "Password has been reset successfully" });
-    });
+    user.password = password;
+    await user.save();
+    return res
+      .status(200)
+      .json({ message: "Password has been reset successfully" });
   } catch (error) {
🤖 Fix all issues with AI agents
In `@backend/models/organizationSchema.js`:
- Around line 70-78: The organizationSchema currently defines created_at and
updated_at with default: Date.now so updated_at won't change on document
updates; instead remove those manual fields and enable Mongoose's automatic
timestamps by adding the timestamps: true option when defining
organizationSchema (matching userSchema and positionHolderSchema), or
alternatively implement a pre('save') / pre('findOneAndUpdate') hook on
organizationSchema to set updated_at = Date.now() on modifications; update any
code that referenced the manual created_at/updated_at fields if their names
change (e.g., to createdAt/updatedAt) to match Mongoose's timestamp naming if
you choose timestamps: true.

In `@backend/models/userSchema.js`:
- Around line 97-100: The pre-save hook userSchema.pre currently passes
Number(process.env.SALT) into bcrypt.hash which becomes NaN if SALT is
undefined; update the hook to validate/parses the env value and provide a safe
default (e.g., 10) before calling bcrypt.hash, or throw a clear error at startup
if SALT must be provided; specifically ensure the value passed to bcrypt.hash is
a finite integer (use parsing + fallback) and add a clear error or default at
initialization so bcrypt.hash never receives NaN.
- Around line 31-39: personal_info.email currently lacks a uniqueness constraint
and can diverge from username via the profile update endpoint; add a uniqueness
index to the Mongoose schema (set personal_info.email: { type: String, required:
true, unique: true }) in userSchema.js and also add a schema-level validation or
pre('save')/pre('findOneAndUpdate') hook that enforces personal_info.email ===
username (or rejects updates that attempt to change email independently) so the
profile update handler cannot create duplicates—also update the profile update
logic to validate/reject mismatched email changes to keep lookups consistent.

In `@backend/routes/auth.js`:
- Around line 57-67: The current access token created by jwt.sign(payload,
secretKey, { expiresIn: "5m" }) and set in res.cookie("token", token, { maxAge:
5 * 60 * 1000 }) is too short-lived; either increase the session duration by
updating the expiresIn and corresponding maxAge to a realistic value (e.g.,
hours/days) or implement a refresh-token flow: issue a short-lived access token
via jwt.sign and a long-lived refresh token (stored as an HttpOnly cookie), add
a /refreshToken endpoint that validates and rotates the refresh token
(persisting it or its identifier server-side, e.g., in the User model), and
update jwt-signing and cookie logic in the login handler (the payload creation
in this file) to support issuing both tokens and renewing access tokens when
/refreshToken is called.
- Line 120: Remove the insecure console.log(newUser) that prints the entire user
document (including hashed password); instead either delete the log entirely or
replace it with a safe log that only outputs non-sensitive identifiers (e.g.,
newUser.id or newUser.email) or a simple confirmation message. Locate the
statement referencing newUser in the user creation flow (the console.log call)
and remove or change it so no password or full document is emitted to server
logs.
- Around line 48-50: The route handler incorrectly continues executing after
sending the 401 response for Google-authenticated users; add an immediate return
after res.status(401).json({ message: "Use Google sign-in for this acount." })
so execution does not fall through to bcrypt.compare(password, user.password)
(and avoid calling bcrypt.compare when user.password is undefined). Ensure the
early-return happens in the same conditional that checks user.strategy !==
"local" || !user.password.
🧹 Nitpick comments (7)
backend/package.json (1)

56-58: Consider removing unused passport-local and passport-local-mongoose dependencies.

The LocalStrategy import is commented out in passportConfig.js, and the PR replaces Passport-based local auth with manual bcrypt+JWT. These packages appear to be dead weight now.

backend/models/positionSchema.js (1)

1-56: Inconsistent timestamp approach across models.

This schema manually defines created_at without updated_at, while userSchema.js and positionHolderSchema.js use Mongoose's { timestamps: true }. Consider standardizing on one approach across all models for consistency.

backend/models/feedbackSchema.js (2)

14-22: target_id lacks a ref or refPathpopulate() won't work automatically.

If you intend to populate target_id based on target_type, use Mongoose's refPath to enable dynamic references:

Proposed fix using refPath
   target_id: {
     type: mongoose.Schema.Types.ObjectId,
-    //required: true,
-    // We'll dynamically interpret this field based on target_type
+    refPath: "target_type",
   },
   target_type: {
     type: String,
     required: true,
+    enum: ["Event", "Organizational_Unit", "Position"], // constrain to valid model names
   },

28-32: Remove commented-out category code.

If this is no longer needed, remove it to keep the schema clean. It can always be retrieved from version control.

backend/routes/auth.js (2)

5-5: Remove commented-out import.

Dead code — if isIITBhilaiEmail is no longer used in this file, remove the comment entirely.

🧹 Proposed fix
-//const isIITBhilaiEmail = require("../utils/isIITBhilaiEmail");

78-88: role is hardcoded but still validated by registerValidate — consider removing it from validation.

role is hardcoded to "STUDENT" on line 81 (good — addresses the previous privilege-escalation concern), but it's still passed to registerValidate.safeParse() which validates role: zod.string().min(5). This validation of a server-controlled constant is unnecessary. Consider removing role from the Zod schema or from the safeParse call to keep the validation schema aligned with actual user input.

backend/models/eventSchema.js (1)

107-115: updated_at won't auto-update on document modifications.

default: Date.now only sets the value on creation. On subsequent updates, updated_at will remain stale unless manually set. Consider using Mongoose's built-in timestamps option which automatically manages both createdAt and updatedAt:

♻️ Proposed refactor

Remove the manual created_at and updated_at fields and add timestamps: true to the schema options:

-  created_at: {
-    type: Date,
-    default: Date.now,
-  },
-  updated_at: {
-    type: Date,
-    default: Date.now,
-  },
-});
+});
+
+eventSchema.set("timestamps", true);

Or pass it as a schema option:

const eventSchema = new mongoose.Schema({ /* ... */ }, { timestamps: true });

Note: This renames the fields to createdAt/updatedAt (camelCase). If you need snake_case, you can configure it:

{ timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' } }

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 9, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://api.github.com/repos/OpenLake/Student_Database_COSA/issues/comments/3820089225","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- walkthrough_start -->\n\n## Walkthrough\n\nThis PR refactors authentication from Passport LocalStrategy to JWT tokens with bcrypt, introduces Google OAuth with IIT Bhilai email domain validation, reorganizes monolithic schema into separate model files, adds certificate batch management with multi-level approval workflows, and updates route imports for consistency.\n\n## Changes\n\n|Cohort / File(s)|Summary|\n|---|---|\n|**Database & Configuration** <br> `backend/config/db.js`, `backend/config/passportConfig.js`|MongoDB connection initialization via mongoose with connectDB/closeDB utilities; Google OAuth strategy with email domain validation and automatic user creation.|\n|**Schema Reorganization - New Individual Models** <br> `backend/models/userSchema.js`, `backend/models/achievementSchema.js`, `backend/models/eventSchema.js`, `backend/models/feedbackSchema.js`, `backend/models/organizationSchema.js`, `backend/models/positionHolderSchema.js`, `backend/models/positionSchema.js`|Extracted individual Mongoose schema definitions from monolithic schema file; User model includes bcrypt pre-save password hashing, Google/local strategy support, and nested personal/academic/contact info.|\n|**Schema Reorganization - Consolidation** <br> `backend/models/schema.js`, `backend/models/passportConfig.js`|Removed User, Feedback, Achievement, Event, Position, PositionHolder, OrganizationalUnit exports; retained only UserSkill, Skill, Announcement; removed passport-local-mongoose and mongoose-findorcreate plugins; updated User import path.|\n|**Certificate Management** <br> `backend/models/certificateSchema.js`, `backend/controllers/certificateController.js`, `backend/routes/certificateRoutes.js`|New CertificateBatch and Certificate schemas with multi-level approval workflow (PendingL1/PendingL2 states); controller validates batch creation, enforces role-based authorization, resolves approvers (General Secretary/President), and validates user participation.|\n|**Authentication Refactor** <br> `backend/routes/auth.js`, `backend/middlewares/isAuthenticated.js`|Replaced Passport LocalStrategy with custom JWT-based authentication; manual password hashing/verification via bcrypt; added jwtIsAuthenticated middleware for token verification; Google OAuth routes retained with updated User model references.|\n|**Route Import Updates** <br> `backend/routes/achievements.js`, `backend/routes/analytics.js`, `backend/routes/announcements.js`, `backend/routes/dashboard.js`, `backend/routes/events.js`, `backend/routes/feedbackRoutes.js`, `backend/routes/onboarding.js`, `backend/routes/orgUnit.js`, `backend/routes/positionRoutes.js`, `backend/routes/profile.js`, `backend/routes/skillsRoutes.js`|Updated isAuthenticated imports from default to named exports; adjusted Achievement import from schema.js to achievementSchema.js; standardized string quotes and formatting.|\n|**Utilities** <br> `backend/utils/authValidate.js`, `backend/utils/batchValidate.js`|New Zod validators for login/registration (IIT Bhilai email domain + password constraints) and batch creation (title, unit_id, template_id, users with ObjectId validation).|\n|**Server Setup & Dependencies** <br> `backend/index.js`, `backend/package.json`, `package.json`|Added connectDB invocation, cookie-parser middleware, and certificate routes at `/api/certificate-batches`; replaced body-parser with express.json(); updated passport import path; added bcrypt, cookie-parser, zod dependencies; removed passport-local packages.|\n\n## Sequence Diagram(s)\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant Server as Auth Handler\n    participant UserDB as User DB\n    participant BCrypt as BCrypt\n    participant JWT as JWT Signer\n\n    Client->>Server: POST /login (username, password)\n    Server->>Server: Validate input with loginValidate\n    alt Invalid Input\n        Server-->>Client: 400 Validation Error\n    end\n    Server->>UserDB: Query user by username\n    alt User Not Found\n        UserDB-->>Server: null\n        Server-->>Client: 401 Invalid credentials\n    end\n    UserDB-->>Server: User document\n    Server->>BCrypt: Verify password hash\n    alt Password Mismatch\n        BCrypt-->>Server: false\n        Server-->>Client: 401 Invalid credentials\n    end\n    BCrypt-->>Server: true\n    Server->>JWT: Sign token (user_id, 30 min expiry)\n    JWT-->>Server: JWT token\n    Server->>Server: Set httpOnly cookie\n    Server-->>Client: 200 Login success + cookie\n```\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant GoogleOAuth as Google OAuth\n    participant Server as Callback Handler\n    participant UserDB as User DB\n\n    Client->>GoogleOAuth: Request Google auth\n    GoogleOAuth-->>Client: Redirect to Google login\n    Client->>GoogleOAuth: Authenticate\n    GoogleOAuth-->>Server: Callback with profile\n    Server->>Server: Validate IIT Bhilai email domain\n    alt Non-IIT Email\n        Server-->>Client: 403 Invalid domain\n    end\n    Server->>UserDB: Query user by email\n    alt User Exists\n        UserDB-->>Server: User document\n    else User Not Found\n        Server->>UserDB: Create new user (STUDENT role, google strategy)\n        UserDB-->>Server: New user document\n    end\n    Server->>Server: Serialize user\n    Server-->>Client: 200 Auth success\n```\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant CertController as Certificate Controller\n    participant UserDB as User DB\n    participant OrgDB as Organization DB\n    participant ApproverDB as Approver Lookup\n    participant CertDB as Certificate DB\n\n    Client->>CertController: POST /api/certificate-batches (batch data)\n    CertController->>CertController: Validate with jwtIsAuthenticated\n    CertController->>UserDB: Fetch initiator user\n    alt User Not Found\n        UserDB-->>CertController: null\n        CertController-->>Client: 404 User not found\n    end\n    CertController->>CertController: Check role = CLUB_COORDINATOR\n    alt Unauthorized\n        CertController-->>Client: 403 Forbidden\n    end\n    CertController->>CertController: Validate batch schema (title, unit_id, users)\n    alt Validation Fails\n        CertController-->>Client: 400 Validation errors\n    end\n    CertController->>OrgDB: Fetch organization unit (club)\n    CertController->>CertController: Verify coordinator in unit\n    alt Not coordinator of club\n        CertController-->>Client: 403 Forbidden\n    end\n    CertController->>OrgDB: Validate parent (Council)\n    CertController->>ApproverDB: Resolve General Secretary\n    CertController->>ApproverDB: Resolve President\n    alt Approvers Not Found\n        ApproverDB-->>CertController: null\n        CertController-->>Client: 500 Approvers not found\n    end\n    CertController->>CertController: Validate each user ID\n    alt Invalid user ID\n        CertController-->>Client: 400 Invalid user\n    end\n    CertController->>CertDB: Create CertificateBatch\n    CertDB-->>CertController: Batch created\n    CertController-->>Client: 200 Batch created with details\n```\n\n## Estimated code review effort\n\n🎯 4 (Complex) | ⏱️ ~75 minutes\n\n## Poem\n\n> 🐰 *Hop hop, the auth now flows with tokens so bright,*  \n> *JWT and bcrypt replacing old Passport's light,*  \n> *Schemas split clean like carrots in spring,*  \n> *Certificates batch with approvals to bring,*  \n> *Google's domain guard says \"Welcome home, friend!\"*\n\n<!-- walkthrough_end -->\n\n\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 1 | ❌ 2</summary>\n\n<details>\n<summary>❌ Failed checks (2 warnings)</summary>\n\n|     Check name     | Status     | Explanation                                                                                          | Resolution                                                                                                                                       |\n| :----------------: | :--------- | :--------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |\n|     Title check    | ⚠️ Warning | The PR title 'Refactor/login register' is vague and lacks specificity about the actual changes made. | Use a more descriptive title such as 'Refactor authentication with manual login/register and Zod validation' to clearly convey the main changes. |\n| Docstring Coverage | ⚠️ Warning | Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.                | Write docstrings for the functions missing them to satisfy the coverage threshold.                                                               |\n\n</details>\n<details>\n<summary>✅ Passed checks (1 passed)</summary>\n\n|     Check name    | Status   | Explanation                                                                                                                                                     |\n| :---------------: | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Description check | ✅ Passed | The PR description is comprehensive and well-structured, covering all key aspects including changes, motivation, testing, and deployment notes as per template. |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing touches</summary>\n\n- [ ] <!-- {\"checkboxId\": \"7962f53c-55bc-4827-bfbf-6a18da830691\"} --> 📝 Generate docstrings\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"07f1e7d6-8a8e-4e23-9900-8731c2c87f58\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Post copyable unit tests in a comment\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=OpenLake/Student_Database_COSA&utm_content=222)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n\n<!-- internal state start -->\n\n\n<!-- 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 -->\n\n<!-- internal state end -->"},"request":{"retryCount":3,"retries":3,"retryAfter":16}},"response":{"url":"https://api.github.com/repos/OpenLake/Student_Database_COSA/issues/comments/3820089225","status":500,"headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset","content-length":"0","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Mon, 09 Feb 2026 16:23:32 GMT","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"github.com","strict-transport-security":"max-age=31536000; includeSubdomains; preload","vary":"Accept-Encoding, Accept, X-Requested-With","x-accepted-github-permissions":"issues=write; pull_requests=write","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-api-version-selected":"2022-11-28","x-github-media-type":"github.v3; format=json","x-github-request-id":"A03C:51096:EAFCFE:3EFD3C7:698A0A03","x-ratelimit-limit":"8650","x-ratelimit-remaining":"8587","x-ratelimit-reset":"1770657425","x-ratelimit-resource":"core","x-ratelimit-used":"63","x-xss-protection":"0"},"data":""}}

3 similar comments
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 9, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://api.github.com/repos/OpenLake/Student_Database_COSA/issues/comments/3820089225","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- walkthrough_start -->\n\n## Walkthrough\n\nThis PR refactors authentication from Passport LocalStrategy to JWT tokens with bcrypt, introduces Google OAuth with IIT Bhilai email domain validation, reorganizes monolithic schema into separate model files, adds certificate batch management with multi-level approval workflows, and updates route imports for consistency.\n\n## Changes\n\n|Cohort / File(s)|Summary|\n|---|---|\n|**Database & Configuration** <br> `backend/config/db.js`, `backend/config/passportConfig.js`|MongoDB connection initialization via mongoose with connectDB/closeDB utilities; Google OAuth strategy with email domain validation and automatic user creation.|\n|**Schema Reorganization - New Individual Models** <br> `backend/models/userSchema.js`, `backend/models/achievementSchema.js`, `backend/models/eventSchema.js`, `backend/models/feedbackSchema.js`, `backend/models/organizationSchema.js`, `backend/models/positionHolderSchema.js`, `backend/models/positionSchema.js`|Extracted individual Mongoose schema definitions from monolithic schema file; User model includes bcrypt pre-save password hashing, Google/local strategy support, and nested personal/academic/contact info.|\n|**Schema Reorganization - Consolidation** <br> `backend/models/schema.js`, `backend/models/passportConfig.js`|Removed User, Feedback, Achievement, Event, Position, PositionHolder, OrganizationalUnit exports; retained only UserSkill, Skill, Announcement; removed passport-local-mongoose and mongoose-findorcreate plugins; updated User import path.|\n|**Certificate Management** <br> `backend/models/certificateSchema.js`, `backend/controllers/certificateController.js`, `backend/routes/certificateRoutes.js`|New CertificateBatch and Certificate schemas with multi-level approval workflow (PendingL1/PendingL2 states); controller validates batch creation, enforces role-based authorization, resolves approvers (General Secretary/President), and validates user participation.|\n|**Authentication Refactor** <br> `backend/routes/auth.js`, `backend/middlewares/isAuthenticated.js`|Replaced Passport LocalStrategy with custom JWT-based authentication; manual password hashing/verification via bcrypt; added jwtIsAuthenticated middleware for token verification; Google OAuth routes retained with updated User model references.|\n|**Route Import Updates** <br> `backend/routes/achievements.js`, `backend/routes/analytics.js`, `backend/routes/announcements.js`, `backend/routes/dashboard.js`, `backend/routes/events.js`, `backend/routes/feedbackRoutes.js`, `backend/routes/onboarding.js`, `backend/routes/orgUnit.js`, `backend/routes/positionRoutes.js`, `backend/routes/profile.js`, `backend/routes/skillsRoutes.js`|Updated isAuthenticated imports from default to named exports; adjusted Achievement import from schema.js to achievementSchema.js; standardized string quotes and formatting.|\n|**Utilities** <br> `backend/utils/authValidate.js`, `backend/utils/batchValidate.js`|New Zod validators for login/registration (IIT Bhilai email domain + password constraints) and batch creation (title, unit_id, template_id, users with ObjectId validation).|\n|**Server Setup & Dependencies** <br> `backend/index.js`, `backend/package.json`, `package.json`|Added connectDB invocation, cookie-parser middleware, and certificate routes at `/api/certificate-batches`; replaced body-parser with express.json(); updated passport import path; added bcrypt, cookie-parser, zod dependencies; removed passport-local packages.|\n\n## Sequence Diagram(s)\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant Server as Auth Handler\n    participant UserDB as User DB\n    participant BCrypt as BCrypt\n    participant JWT as JWT Signer\n\n    Client->>Server: POST /login (username, password)\n    Server->>Server: Validate input with loginValidate\n    alt Invalid Input\n        Server-->>Client: 400 Validation Error\n    end\n    Server->>UserDB: Query user by username\n    alt User Not Found\n        UserDB-->>Server: null\n        Server-->>Client: 401 Invalid credentials\n    end\n    UserDB-->>Server: User document\n    Server->>BCrypt: Verify password hash\n    alt Password Mismatch\n        BCrypt-->>Server: false\n        Server-->>Client: 401 Invalid credentials\n    end\n    BCrypt-->>Server: true\n    Server->>JWT: Sign token (user_id, 30 min expiry)\n    JWT-->>Server: JWT token\n    Server->>Server: Set httpOnly cookie\n    Server-->>Client: 200 Login success + cookie\n```\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant GoogleOAuth as Google OAuth\n    participant Server as Callback Handler\n    participant UserDB as User DB\n\n    Client->>GoogleOAuth: Request Google auth\n    GoogleOAuth-->>Client: Redirect to Google login\n    Client->>GoogleOAuth: Authenticate\n    GoogleOAuth-->>Server: Callback with profile\n    Server->>Server: Validate IIT Bhilai email domain\n    alt Non-IIT Email\n        Server-->>Client: 403 Invalid domain\n    end\n    Server->>UserDB: Query user by email\n    alt User Exists\n        UserDB-->>Server: User document\n    else User Not Found\n        Server->>UserDB: Create new user (STUDENT role, google strategy)\n        UserDB-->>Server: New user document\n    end\n    Server->>Server: Serialize user\n    Server-->>Client: 200 Auth success\n```\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant CertController as Certificate Controller\n    participant UserDB as User DB\n    participant OrgDB as Organization DB\n    participant ApproverDB as Approver Lookup\n    participant CertDB as Certificate DB\n\n    Client->>CertController: POST /api/certificate-batches (batch data)\n    CertController->>CertController: Validate with jwtIsAuthenticated\n    CertController->>UserDB: Fetch initiator user\n    alt User Not Found\n        UserDB-->>CertController: null\n        CertController-->>Client: 404 User not found\n    end\n    CertController->>CertController: Check role = CLUB_COORDINATOR\n    alt Unauthorized\n        CertController-->>Client: 403 Forbidden\n    end\n    CertController->>CertController: Validate batch schema (title, unit_id, users)\n    alt Validation Fails\n        CertController-->>Client: 400 Validation errors\n    end\n    CertController->>OrgDB: Fetch organization unit (club)\n    CertController->>CertController: Verify coordinator in unit\n    alt Not coordinator of club\n        CertController-->>Client: 403 Forbidden\n    end\n    CertController->>OrgDB: Validate parent (Council)\n    CertController->>ApproverDB: Resolve General Secretary\n    CertController->>ApproverDB: Resolve President\n    alt Approvers Not Found\n        ApproverDB-->>CertController: null\n        CertController-->>Client: 500 Approvers not found\n    end\n    CertController->>CertController: Validate each user ID\n    alt Invalid user ID\n        CertController-->>Client: 400 Invalid user\n    end\n    CertController->>CertDB: Create CertificateBatch\n    CertDB-->>CertController: Batch created\n    CertController-->>Client: 200 Batch created with details\n```\n\n## Estimated code review effort\n\n🎯 4 (Complex) | ⏱️ ~75 minutes\n\n## Poem\n\n> 🐰 *Hop hop, the auth now flows with tokens so bright,*  \n> *JWT and bcrypt replacing old Passport's light,*  \n> *Schemas split clean like carrots in spring,*  \n> *Certificates batch with approvals to bring,*  \n> *Google's domain guard says \"Welcome home, friend!\"*\n\n<!-- walkthrough_end -->\n\n\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 1 | ❌ 2</summary>\n\n<details>\n<summary>❌ Failed checks (2 warnings)</summary>\n\n|     Check name     | Status     | Explanation                                                                                          | Resolution                                                                                                                                       |\n| :----------------: | :--------- | :--------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |\n|     Title check    | ⚠️ Warning | The PR title 'Refactor/login register' is vague and lacks specificity about the actual changes made. | Use a more descriptive title such as 'Refactor authentication with manual login/register and Zod validation' to clearly convey the main changes. |\n| Docstring Coverage | ⚠️ Warning | Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.                | Write docstrings for the functions missing them to satisfy the coverage threshold.                                                               |\n\n</details>\n<details>\n<summary>✅ Passed checks (1 passed)</summary>\n\n|     Check name    | Status   | Explanation                                                                                                                                                     |\n| :---------------: | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Description check | ✅ Passed | The PR description is comprehensive and well-structured, covering all key aspects including changes, motivation, testing, and deployment notes as per template. |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing touches</summary>\n\n- [ ] <!-- {\"checkboxId\": \"7962f53c-55bc-4827-bfbf-6a18da830691\"} --> 📝 Generate docstrings\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"07f1e7d6-8a8e-4e23-9900-8731c2c87f58\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Post copyable unit tests in a comment\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=OpenLake/Student_Database_COSA&utm_content=222)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n\n<!-- internal state start -->\n\n\n<!-- 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 -->\n\n<!-- internal state end -->"},"request":{"retryCount":3,"retries":3,"retryAfter":16}},"response":{"url":"https://api.github.com/repos/OpenLake/Student_Database_COSA/issues/comments/3820089225","status":500,"headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset","content-length":"0","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Mon, 09 Feb 2026 16:23:32 GMT","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"github.com","strict-transport-security":"max-age=31536000; includeSubdomains; preload","vary":"Accept-Encoding, Accept, X-Requested-With","x-accepted-github-permissions":"issues=write; pull_requests=write","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-api-version-selected":"2022-11-28","x-github-media-type":"github.v3; format=json","x-github-request-id":"A03C:51096:EAFCFE:3EFD3C7:698A0A03","x-ratelimit-limit":"8650","x-ratelimit-remaining":"8587","x-ratelimit-reset":"1770657425","x-ratelimit-resource":"core","x-ratelimit-used":"63","x-xss-protection":"0"},"data":""}}

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 9, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://api.github.com/repos/OpenLake/Student_Database_COSA/issues/comments/3820089225","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- walkthrough_start -->\n\n## Walkthrough\n\nThis PR refactors authentication from Passport LocalStrategy to JWT tokens with bcrypt, introduces Google OAuth with IIT Bhilai email domain validation, reorganizes monolithic schema into separate model files, adds certificate batch management with multi-level approval workflows, and updates route imports for consistency.\n\n## Changes\n\n|Cohort / File(s)|Summary|\n|---|---|\n|**Database & Configuration** <br> `backend/config/db.js`, `backend/config/passportConfig.js`|MongoDB connection initialization via mongoose with connectDB/closeDB utilities; Google OAuth strategy with email domain validation and automatic user creation.|\n|**Schema Reorganization - New Individual Models** <br> `backend/models/userSchema.js`, `backend/models/achievementSchema.js`, `backend/models/eventSchema.js`, `backend/models/feedbackSchema.js`, `backend/models/organizationSchema.js`, `backend/models/positionHolderSchema.js`, `backend/models/positionSchema.js`|Extracted individual Mongoose schema definitions from monolithic schema file; User model includes bcrypt pre-save password hashing, Google/local strategy support, and nested personal/academic/contact info.|\n|**Schema Reorganization - Consolidation** <br> `backend/models/schema.js`, `backend/models/passportConfig.js`|Removed User, Feedback, Achievement, Event, Position, PositionHolder, OrganizationalUnit exports; retained only UserSkill, Skill, Announcement; removed passport-local-mongoose and mongoose-findorcreate plugins; updated User import path.|\n|**Certificate Management** <br> `backend/models/certificateSchema.js`, `backend/controllers/certificateController.js`, `backend/routes/certificateRoutes.js`|New CertificateBatch and Certificate schemas with multi-level approval workflow (PendingL1/PendingL2 states); controller validates batch creation, enforces role-based authorization, resolves approvers (General Secretary/President), and validates user participation.|\n|**Authentication Refactor** <br> `backend/routes/auth.js`, `backend/middlewares/isAuthenticated.js`|Replaced Passport LocalStrategy with custom JWT-based authentication; manual password hashing/verification via bcrypt; added jwtIsAuthenticated middleware for token verification; Google OAuth routes retained with updated User model references.|\n|**Route Import Updates** <br> `backend/routes/achievements.js`, `backend/routes/analytics.js`, `backend/routes/announcements.js`, `backend/routes/dashboard.js`, `backend/routes/events.js`, `backend/routes/feedbackRoutes.js`, `backend/routes/onboarding.js`, `backend/routes/orgUnit.js`, `backend/routes/positionRoutes.js`, `backend/routes/profile.js`, `backend/routes/skillsRoutes.js`|Updated isAuthenticated imports from default to named exports; adjusted Achievement import from schema.js to achievementSchema.js; standardized string quotes and formatting.|\n|**Utilities** <br> `backend/utils/authValidate.js`, `backend/utils/batchValidate.js`|New Zod validators for login/registration (IIT Bhilai email domain + password constraints) and batch creation (title, unit_id, template_id, users with ObjectId validation).|\n|**Server Setup & Dependencies** <br> `backend/index.js`, `backend/package.json`, `package.json`|Added connectDB invocation, cookie-parser middleware, and certificate routes at `/api/certificate-batches`; replaced body-parser with express.json(); updated passport import path; added bcrypt, cookie-parser, zod dependencies; removed passport-local packages.|\n\n## Sequence Diagram(s)\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant Server as Auth Handler\n    participant UserDB as User DB\n    participant BCrypt as BCrypt\n    participant JWT as JWT Signer\n\n    Client->>Server: POST /login (username, password)\n    Server->>Server: Validate input with loginValidate\n    alt Invalid Input\n        Server-->>Client: 400 Validation Error\n    end\n    Server->>UserDB: Query user by username\n    alt User Not Found\n        UserDB-->>Server: null\n        Server-->>Client: 401 Invalid credentials\n    end\n    UserDB-->>Server: User document\n    Server->>BCrypt: Verify password hash\n    alt Password Mismatch\n        BCrypt-->>Server: false\n        Server-->>Client: 401 Invalid credentials\n    end\n    BCrypt-->>Server: true\n    Server->>JWT: Sign token (user_id, 30 min expiry)\n    JWT-->>Server: JWT token\n    Server->>Server: Set httpOnly cookie\n    Server-->>Client: 200 Login success + cookie\n```\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant GoogleOAuth as Google OAuth\n    participant Server as Callback Handler\n    participant UserDB as User DB\n\n    Client->>GoogleOAuth: Request Google auth\n    GoogleOAuth-->>Client: Redirect to Google login\n    Client->>GoogleOAuth: Authenticate\n    GoogleOAuth-->>Server: Callback with profile\n    Server->>Server: Validate IIT Bhilai email domain\n    alt Non-IIT Email\n        Server-->>Client: 403 Invalid domain\n    end\n    Server->>UserDB: Query user by email\n    alt User Exists\n        UserDB-->>Server: User document\n    else User Not Found\n        Server->>UserDB: Create new user (STUDENT role, google strategy)\n        UserDB-->>Server: New user document\n    end\n    Server->>Server: Serialize user\n    Server-->>Client: 200 Auth success\n```\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant CertController as Certificate Controller\n    participant UserDB as User DB\n    participant OrgDB as Organization DB\n    participant ApproverDB as Approver Lookup\n    participant CertDB as Certificate DB\n\n    Client->>CertController: POST /api/certificate-batches (batch data)\n    CertController->>CertController: Validate with jwtIsAuthenticated\n    CertController->>UserDB: Fetch initiator user\n    alt User Not Found\n        UserDB-->>CertController: null\n        CertController-->>Client: 404 User not found\n    end\n    CertController->>CertController: Check role = CLUB_COORDINATOR\n    alt Unauthorized\n        CertController-->>Client: 403 Forbidden\n    end\n    CertController->>CertController: Validate batch schema (title, unit_id, users)\n    alt Validation Fails\n        CertController-->>Client: 400 Validation errors\n    end\n    CertController->>OrgDB: Fetch organization unit (club)\n    CertController->>CertController: Verify coordinator in unit\n    alt Not coordinator of club\n        CertController-->>Client: 403 Forbidden\n    end\n    CertController->>OrgDB: Validate parent (Council)\n    CertController->>ApproverDB: Resolve General Secretary\n    CertController->>ApproverDB: Resolve President\n    alt Approvers Not Found\n        ApproverDB-->>CertController: null\n        CertController-->>Client: 500 Approvers not found\n    end\n    CertController->>CertController: Validate each user ID\n    alt Invalid user ID\n        CertController-->>Client: 400 Invalid user\n    end\n    CertController->>CertDB: Create CertificateBatch\n    CertDB-->>CertController: Batch created\n    CertController-->>Client: 200 Batch created with details\n```\n\n## Estimated code review effort\n\n🎯 4 (Complex) | ⏱️ ~75 minutes\n\n## Poem\n\n> 🐰 *Hop hop, the auth now flows with tokens so bright,*  \n> *JWT and bcrypt replacing old Passport's light,*  \n> *Schemas split clean like carrots in spring,*  \n> *Certificates batch with approvals to bring,*  \n> *Google's domain guard says \"Welcome home, friend!\"*\n\n<!-- walkthrough_end -->\n\n\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 1 | ❌ 2</summary>\n\n<details>\n<summary>❌ Failed checks (2 warnings)</summary>\n\n|     Check name     | Status     | Explanation                                                                                          | Resolution                                                                                                                                       |\n| :----------------: | :--------- | :--------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |\n|     Title check    | ⚠️ Warning | The PR title 'Refactor/login register' is vague and lacks specificity about the actual changes made. | Use a more descriptive title such as 'Refactor authentication with manual login/register and Zod validation' to clearly convey the main changes. |\n| Docstring Coverage | ⚠️ Warning | Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.                | Write docstrings for the functions missing them to satisfy the coverage threshold.                                                               |\n\n</details>\n<details>\n<summary>✅ Passed checks (1 passed)</summary>\n\n|     Check name    | Status   | Explanation                                                                                                                                                     |\n| :---------------: | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Description check | ✅ Passed | The PR description is comprehensive and well-structured, covering all key aspects including changes, motivation, testing, and deployment notes as per template. |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing touches</summary>\n\n- [ ] <!-- {\"checkboxId\": \"7962f53c-55bc-4827-bfbf-6a18da830691\"} --> 📝 Generate docstrings\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"07f1e7d6-8a8e-4e23-9900-8731c2c87f58\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Post copyable unit tests in a comment\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=OpenLake/Student_Database_COSA&utm_content=222)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n\n<!-- internal state start -->\n\n\n<!-- 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 -->\n\n<!-- internal state end -->"},"request":{"retryCount":3,"retries":3,"retryAfter":16}},"response":{"url":"https://api.github.com/repos/OpenLake/Student_Database_COSA/issues/comments/3820089225","status":500,"headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset","content-length":"0","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Mon, 09 Feb 2026 16:23:32 GMT","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"github.com","strict-transport-security":"max-age=31536000; includeSubdomains; preload","vary":"Accept-Encoding, Accept, X-Requested-With","x-accepted-github-permissions":"issues=write; pull_requests=write","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-api-version-selected":"2022-11-28","x-github-media-type":"github.v3; format=json","x-github-request-id":"A03C:51096:EAFCFE:3EFD3C7:698A0A03","x-ratelimit-limit":"8650","x-ratelimit-remaining":"8587","x-ratelimit-reset":"1770657425","x-ratelimit-resource":"core","x-ratelimit-used":"63","x-xss-protection":"0"},"data":""}}

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 9, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://api.github.com/repos/OpenLake/Student_Database_COSA/issues/comments/3820089225","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- walkthrough_start -->\n\n## Walkthrough\n\nThis PR refactors authentication from Passport LocalStrategy to JWT tokens with bcrypt, introduces Google OAuth with IIT Bhilai email domain validation, reorganizes monolithic schema into separate model files, adds certificate batch management with multi-level approval workflows, and updates route imports for consistency.\n\n## Changes\n\n|Cohort / File(s)|Summary|\n|---|---|\n|**Database & Configuration** <br> `backend/config/db.js`, `backend/config/passportConfig.js`|MongoDB connection initialization via mongoose with connectDB/closeDB utilities; Google OAuth strategy with email domain validation and automatic user creation.|\n|**Schema Reorganization - New Individual Models** <br> `backend/models/userSchema.js`, `backend/models/achievementSchema.js`, `backend/models/eventSchema.js`, `backend/models/feedbackSchema.js`, `backend/models/organizationSchema.js`, `backend/models/positionHolderSchema.js`, `backend/models/positionSchema.js`|Extracted individual Mongoose schema definitions from monolithic schema file; User model includes bcrypt pre-save password hashing, Google/local strategy support, and nested personal/academic/contact info.|\n|**Schema Reorganization - Consolidation** <br> `backend/models/schema.js`, `backend/models/passportConfig.js`|Removed User, Feedback, Achievement, Event, Position, PositionHolder, OrganizationalUnit exports; retained only UserSkill, Skill, Announcement; removed passport-local-mongoose and mongoose-findorcreate plugins; updated User import path.|\n|**Certificate Management** <br> `backend/models/certificateSchema.js`, `backend/controllers/certificateController.js`, `backend/routes/certificateRoutes.js`|New CertificateBatch and Certificate schemas with multi-level approval workflow (PendingL1/PendingL2 states); controller validates batch creation, enforces role-based authorization, resolves approvers (General Secretary/President), and validates user participation.|\n|**Authentication Refactor** <br> `backend/routes/auth.js`, `backend/middlewares/isAuthenticated.js`|Replaced Passport LocalStrategy with custom JWT-based authentication; manual password hashing/verification via bcrypt; added jwtIsAuthenticated middleware for token verification; Google OAuth routes retained with updated User model references.|\n|**Route Import Updates** <br> `backend/routes/achievements.js`, `backend/routes/analytics.js`, `backend/routes/announcements.js`, `backend/routes/dashboard.js`, `backend/routes/events.js`, `backend/routes/feedbackRoutes.js`, `backend/routes/onboarding.js`, `backend/routes/orgUnit.js`, `backend/routes/positionRoutes.js`, `backend/routes/profile.js`, `backend/routes/skillsRoutes.js`|Updated isAuthenticated imports from default to named exports; adjusted Achievement import from schema.js to achievementSchema.js; standardized string quotes and formatting.|\n|**Utilities** <br> `backend/utils/authValidate.js`, `backend/utils/batchValidate.js`|New Zod validators for login/registration (IIT Bhilai email domain + password constraints) and batch creation (title, unit_id, template_id, users with ObjectId validation).|\n|**Server Setup & Dependencies** <br> `backend/index.js`, `backend/package.json`, `package.json`|Added connectDB invocation, cookie-parser middleware, and certificate routes at `/api/certificate-batches`; replaced body-parser with express.json(); updated passport import path; added bcrypt, cookie-parser, zod dependencies; removed passport-local packages.|\n\n## Sequence Diagram(s)\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant Server as Auth Handler\n    participant UserDB as User DB\n    participant BCrypt as BCrypt\n    participant JWT as JWT Signer\n\n    Client->>Server: POST /login (username, password)\n    Server->>Server: Validate input with loginValidate\n    alt Invalid Input\n        Server-->>Client: 400 Validation Error\n    end\n    Server->>UserDB: Query user by username\n    alt User Not Found\n        UserDB-->>Server: null\n        Server-->>Client: 401 Invalid credentials\n    end\n    UserDB-->>Server: User document\n    Server->>BCrypt: Verify password hash\n    alt Password Mismatch\n        BCrypt-->>Server: false\n        Server-->>Client: 401 Invalid credentials\n    end\n    BCrypt-->>Server: true\n    Server->>JWT: Sign token (user_id, 30 min expiry)\n    JWT-->>Server: JWT token\n    Server->>Server: Set httpOnly cookie\n    Server-->>Client: 200 Login success + cookie\n```\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant GoogleOAuth as Google OAuth\n    participant Server as Callback Handler\n    participant UserDB as User DB\n\n    Client->>GoogleOAuth: Request Google auth\n    GoogleOAuth-->>Client: Redirect to Google login\n    Client->>GoogleOAuth: Authenticate\n    GoogleOAuth-->>Server: Callback with profile\n    Server->>Server: Validate IIT Bhilai email domain\n    alt Non-IIT Email\n        Server-->>Client: 403 Invalid domain\n    end\n    Server->>UserDB: Query user by email\n    alt User Exists\n        UserDB-->>Server: User document\n    else User Not Found\n        Server->>UserDB: Create new user (STUDENT role, google strategy)\n        UserDB-->>Server: New user document\n    end\n    Server->>Server: Serialize user\n    Server-->>Client: 200 Auth success\n```\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant CertController as Certificate Controller\n    participant UserDB as User DB\n    participant OrgDB as Organization DB\n    participant ApproverDB as Approver Lookup\n    participant CertDB as Certificate DB\n\n    Client->>CertController: POST /api/certificate-batches (batch data)\n    CertController->>CertController: Validate with jwtIsAuthenticated\n    CertController->>UserDB: Fetch initiator user\n    alt User Not Found\n        UserDB-->>CertController: null\n        CertController-->>Client: 404 User not found\n    end\n    CertController->>CertController: Check role = CLUB_COORDINATOR\n    alt Unauthorized\n        CertController-->>Client: 403 Forbidden\n    end\n    CertController->>CertController: Validate batch schema (title, unit_id, users)\n    alt Validation Fails\n        CertController-->>Client: 400 Validation errors\n    end\n    CertController->>OrgDB: Fetch organization unit (club)\n    CertController->>CertController: Verify coordinator in unit\n    alt Not coordinator of club\n        CertController-->>Client: 403 Forbidden\n    end\n    CertController->>OrgDB: Validate parent (Council)\n    CertController->>ApproverDB: Resolve General Secretary\n    CertController->>ApproverDB: Resolve President\n    alt Approvers Not Found\n        ApproverDB-->>CertController: null\n        CertController-->>Client: 500 Approvers not found\n    end\n    CertController->>CertController: Validate each user ID\n    alt Invalid user ID\n        CertController-->>Client: 400 Invalid user\n    end\n    CertController->>CertDB: Create CertificateBatch\n    CertDB-->>CertController: Batch created\n    CertController-->>Client: 200 Batch created with details\n```\n\n## Estimated code review effort\n\n🎯 4 (Complex) | ⏱️ ~75 minutes\n\n## Poem\n\n> 🐰 *Hop hop, the auth now flows with tokens so bright,*  \n> *JWT and bcrypt replacing old Passport's light,*  \n> *Schemas split clean like carrots in spring,*  \n> *Certificates batch with approvals to bring,*  \n> *Google's domain guard says \"Welcome home, friend!\"*\n\n<!-- walkthrough_end -->\n\n\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 1 | ❌ 2</summary>\n\n<details>\n<summary>❌ Failed checks (2 warnings)</summary>\n\n|     Check name     | Status     | Explanation                                                                                          | Resolution                                                                                                                                       |\n| :----------------: | :--------- | :--------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |\n|     Title check    | ⚠️ Warning | The PR title 'Refactor/login register' is vague and lacks specificity about the actual changes made. | Use a more descriptive title such as 'Refactor authentication with manual login/register and Zod validation' to clearly convey the main changes. |\n| Docstring Coverage | ⚠️ Warning | Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.                | Write docstrings for the functions missing them to satisfy the coverage threshold.                                                               |\n\n</details>\n<details>\n<summary>✅ Passed checks (1 passed)</summary>\n\n|     Check name    | Status   | Explanation                                                                                                                                                     |\n| :---------------: | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Description check | ✅ Passed | The PR description is comprehensive and well-structured, covering all key aspects including changes, motivation, testing, and deployment notes as per template. |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing touches</summary>\n\n- [ ] <!-- {\"checkboxId\": \"7962f53c-55bc-4827-bfbf-6a18da830691\"} --> 📝 Generate docstrings\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"07f1e7d6-8a8e-4e23-9900-8731c2c87f58\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Post copyable unit tests in a comment\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=OpenLake/Student_Database_COSA&utm_content=222)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n\n<!-- internal state start -->\n\n\n<!-- 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 -->\n\n<!-- internal state end -->"},"request":{"retryCount":3,"retries":3,"retryAfter":16}},"response":{"url":"https://api.github.com/repos/OpenLake/Student_Database_COSA/issues/comments/3820089225","status":500,"headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset","content-length":"0","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Mon, 09 Feb 2026 16:23:32 GMT","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"github.com","strict-transport-security":"max-age=31536000; includeSubdomains; preload","vary":"Accept-Encoding, Accept, X-Requested-With","x-accepted-github-permissions":"issues=write; pull_requests=write","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-api-version-selected":"2022-11-28","x-github-media-type":"github.v3; format=json","x-github-request-id":"A03C:51096:EAFCFE:3EFD3C7:698A0A03","x-ratelimit-limit":"8650","x-ratelimit-remaining":"8587","x-ratelimit-reset":"1770657425","x-ratelimit-resource":"core","x-ratelimit-used":"63","x-xss-protection":"0"},"data":""}}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 17

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (9)
backend/routes/onboarding.js (2)

11-11: ⚠️ Potential issue | 🟠 Major

Avoid logging the full req.user object — it may contain PII.

console.log(req.user) can emit emails, names, and other personal information into logs. Replace with a scoped log (e.g., only log req.user._id) or remove it entirely.

Proposed fix
-    console.log(req.user);
+    console.log("Onboarding request for user:", req.user._id);

33-36: ⚠️ Potential issue | 🟠 Major

Raw error object is leaked to the client.

Passing the caught error directly in the JSON response ({ message: "Onboarding failed", error }) can expose stack traces and internal details. Return only a safe message and log the full error server-side.

Proposed fix
     console.error("Onboarding failed:", error);
-    res.status(500).json({ message: "Onboarding failed", error });
+    res.status(500).json({ message: "Onboarding failed" });
backend/routes/feedbackRoutes.js (1)

52-52: ⚠️ Potential issue | 🟡 Minor

Pre-existing typo: "ture" should be "true".

is_anonymous === "ture" will never match a client sending the string "true". This appears to be a pre-existing bug, but since this area is being touched, it's worth fixing now.

Proposed fix
-      is_anonymous: is_anonymous === "ture" || is_anonymous === true,
+      is_anonymous: is_anonymous === "true" || is_anonymous === true,
backend/routes/profile.js (1)

110-111: ⚠️ Potential issue | 🟠 Major

Remove or redact debug logging of user-supplied data.

console.log("Received userId:", userId) and console.log("Received updatedDetails:", updatedDetails) can leak PII (names, emails, phone numbers, dates of birth, social links) to server logs in production.

🛡️ Proposed fix
-    console.log("Received userId:", userId);
-    console.log("Received updatedDetails:", updatedDetails);
backend/routes/auth.js (1)

232-242: ⚠️ Potential issue | 🔴 Critical

user.setPassword() is a passport-local-mongoose method that was removed in this PR.

This PR removed passport-local-mongoose from the project and switched to manual bcrypt-based authentication. The setPassword method no longer exists on the User model, so the password-reset route will throw a TypeError at runtime. Replace it with bcrypt hashing and a direct save.

🐛 Proposed fix
     jwt.verify(token, secret);
-    user.setPassword(password, async (error) => {
-      if (error) {
-        return res.status(500).json({ message: "Error resetting password" });
-      }
-      await user.save();
-      return res
-        .status(200)
-        .json({ message: "Password has been reset successfully" });
-    });
+    user.password = password; // pre-save hook will hash it
+    await user.save();
+    return res
+      .status(200)
+      .json({ message: "Password has been reset successfully" });
backend/routes/announcements.js (2)

103-105: ⚠️ Potential issue | 🟠 Major

User-supplied search is passed directly to new RegExp() — ReDoS risk.

An attacker can craft a search string with pathological regex patterns (e.g., (a+)+$) that cause catastrophic backtracking. Escape special regex characters before constructing the RegExp, or use MongoDB's $text index for search.

🛡️ Proposed fix — escape regex special characters
+    const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
     if (search) {
-      const regex = new RegExp(search, "i");
+      const regex = new RegExp(escapeRegex(search), "i");
       filter.$or = [{ title: regex }, { content: regex }];
     }

4-9: ⚠️ Potential issue | 🔴 Critical

Fix broken imports: Event, OrganizationalUnit, and Position are not exported from schema.js and will be undefined at runtime.

These models were moved to separate files but the imports here were not updated. The findTargetId function will crash on line 18, 25, or 32 when calling .findOne() on undefined models.

Fix: Update imports to use the standalone model files
-const {
-  Announcement,
-  Event,
-  OrganizationalUnit,
-  Position,
-} = require("../models/schema");
+const { Announcement } = require("../models/schema");
+const Event = require("../models/eventSchema");
+const OrganizationalUnit = require("../models/organizationSchema");
+const Position = require("../models/positionSchema");
backend/index.js (2)

68-70: ⚠️ Potential issue | 🟠 Major

Duplicate route mount for announcements.

/api/announcements is mounted twice (lines 68 and 70), which means announcementRoutes handlers will be registered on the same path twice. This can cause handlers to fire twice or produce unexpected behavior.

Proposed fix
 app.use("/api/announcements", announcementRoutes);
 app.use("/api/dashboard", dashboardRoutes);
-app.use("/api/announcements", announcementRoutes);
 app.use("/api/analytics", analyticsRoutes);

40-40: ⚠️ Potential issue | 🟠 Major

Hardcoded session secret.

"keyboard cat" is a well-known placeholder. This should be loaded from an environment variable (e.g. process.env.SESSION_SECRET) to avoid session forgery in production.

🤖 Fix all issues with AI agents
In `@backend/controllers/certificateController.js`:
- Around line 10-148: The createBatch handler currently has no try/catch so any
DB or runtime error will crash the request; wrap the entire async body of
createBatch (all awaits like User.findById, PositionHolder.findOne,
OrganizationalUnit.findById, Promise.all user checks, CertificateBatch.create,
etc.) in a try/catch (or use your existing async error-wrapper middleware) and
in the catch log the error and return res.status(500).json({ message: "Internal
server error", error: /* brief error message or id */ }) to ensure unhandled
rejections are converted to proper HTTP 500 responses.
- Around line 116-128: The validation logic inside the users.map is wrong
because zodObjectId.safeParse(uid) returns an object, so change the check to
inspect validation.success; in the block that builds userChecks (where users.map
calls zodObjectId.safeParse), replace the condition that uses !validation with
!validation.success so invalid IDs return { uid, ok: false, reason: "Invalid ID"
} and subsequent lookups in User.findById only run for successful validations.
- Around line 44-48: The code logs position._id before verifying the result of
Position.findById, which can cause a TypeError if position is null; update the
handler in certificateController.js so you first check the returned value from
Position.findById(positionHolder.position_id) (i.e., if (!position) return
res.status(403).json(...)) and only after that call console.log(position._id) or
otherwise safely access properties of position.
- Around line 18-20: The authorization guard currently lets users with no role
pass because it checks user.role && user.role !== "CLUB_COORDINATOR"; change it
to explicitly deny when role is missing or not the coordinator, e.g. replace the
condition with if (!user.role || user.role !== "CLUB_COORDINATOR") and keep the
existing response using res.status(403).json({ message: "Not authorized to
perform the task" }); so only users whose user.role === "CLUB_COORDINATOR" are
allowed.
- Around line 88-112: The code dereferences query results before null-checking,
causing crashes: after calling User.findOne for gensecObj, Position.findOne for
presidentPosition, PositionHolder.findOne for presidentHolder, and User.findById
for presidentObj, add immediate null/undefined checks and return the 500
response if any are missing (use the same error messages), and remove or move
any console.log calls that access properties like ._id or .user_id until after
those checks; specifically update the logic around gensecObj, presidentPosition,
presidentHolder, and presidentObj to validate each result right after its query
(and only then access ._id or .user_id).

In `@backend/index.js`:
- Around line 29-31: The current connectDB() call silently continues on
failures; update either the connectDB implementation in config/db.js to re-throw
the caught error or call process.exit(1) inside its catch block, or change the
startup flow in backend/index.js to await connectDB() and wrap it in a try/catch
that logs the error and exits (e.g., catch(err) { processLogger.error(..., err);
process.exit(1); }). Ensure you reference the connectDB function (connectDB and
the connectDB() call) and the catch block in config/db.js so the process fails
fast when DB connection fails.

In `@backend/middlewares/isAuthenticated.js`:
- Around line 39-42: The routes import is receiving the entire exported object
instead of the middleware function: update the require in auth.js to pull the
named isAuthenticated export (so the value passed to router.get("/fetchAuth",
isAuthenticated, ...) is the middleware function, not the object) and remove the
large commented-out block in isAuthenticated.js; ensure the module still exports
the named symbols isAuthenticated and jwtIsAuthenticated so other files can
destructure them.

In `@backend/models/certificateSchema.js`:
- Around line 6-9: The ref string on the unit_id field in the certificate schema
is misspelled as "Oraganizational_Unit" causing populate('unit_id') to fail;
update the unit_id schema definition (the ref value) in certificateSchema.js to
the correct registered model name "Organizational_Unit" so it matches the model
created in organizationSchema.js.
- Around line 62-68: The schema-level unique on certificateId causes MongoDB to
block multiple documents with null/undefined (so only one Pending/Rejected
certificate is allowed); remove unique: true from the certificateId field in the
schema and rely on the existing partial index that enforces uniqueness only for
approved certificates (or if you prefer the alternative, replace unique: true
with { unique: true, sparse: true }); update/verify the partial index creation
logic (the index that targets certificateId when status === "Approved") to
ensure uniqueness is enforced at the index level instead of the schema.

In `@backend/models/schema.js`:
- Around line 110-114: schema.js now only exports UserSkill, Skill, Announcement
which breaks many modules that still destructure models like User, Event,
OrganizationalUnit, Position, PositionHolder, Achievement, Feedback; update each
affected import (e.g., backend/routes/announcements.js, profile.js, orgUnit.js,
events.js, onboarding.js, feedbackRoutes.js, positionRoutes.js and controllers
dashboardController.js, certificateController.js, analyticsController.js,
eventControllers.js, middleware isEventContact.js) to import the missing models
directly from their actual schema modules (e.g., userSchema.js for User,
eventSchema.js for Event, organizationSchema.js for OrganizationalUnit,
positionSchema.js for Position/PositionHolder, feedbackSchema.js for Feedback,
achievementSchema.js for Achievement) and adjust the destructuring names
accordingly, or alternatively re-export those models from schema.js if you
intend a single entrypoint; ensure no file destructures undefined symbols after
the change.

In `@backend/package.json`:
- Around line 34-37: The package.json contains unresolved Git merge conflict
markers around the body-parser dependency; remove the conflict markers (<<<<<<<,
=======, >>>>>>>) and delete the "body-parser" entry so package.json becomes
valid JSON; ensure the remaining JSON is properly comma-separated and valid (no
trailing commas) and that nothing else in package.json references body-parser
(index.js uses express.json()), so run npm install to verify.

In `@backend/routes/analytics.js`:
- Around line 25-30: The route registers middleware in the wrong order so
authorizeRole runs before isAuthenticated; update the router.get call that uses
router.get("/club-coordinator", authorizeRole(["CLUB_COORDINATOR"]),
isAuthenticated, controller.getClubCoordinatorAnalytics) to call isAuthenticated
before authorizeRole so req.user is populated (i.e., use
router.get("/club-coordinator", isAuthenticated,
authorizeRole(["CLUB_COORDINATOR"]), controller.getClubCoordinatorAnalytics));
keep controller.getClubCoordinatorAnalytics unchanged.

In `@backend/routes/auth.js`:
- Line 13: The import in backend/routes/auth.js is grabbing the whole exports
object instead of the middleware function; replace the default-style require
with a destructured import so isAuthenticated refers to the function exported by
the module (e.g., change the require to const { isAuthenticated } =
require("../middlewares/isAuthenticated")); ensure any other references to
jwtIsAuthenticated are similarly imported via destructuring if needed so Express
receives actual middleware functions when mounting routes like /fetchAuth.
- Line 64: The jwt.sign call uses an undefined secretKey which will throw a
ReferenceError at runtime; declare and read the secret from an environment
variable (e.g., process.env.JWT_SECRET) near the top of the file and use that
value for secretKey in the token creation (update the jwt.sign call that uses
secretKey). Also add a small guard in the auth initialization (or before issuing
tokens) to throw/log a clear error if the JWT secret is missing so the login
route fails fast with a helpful message.

In `@backend/routes/feedbackRoutes.js`:
- Around line 188-211: The route handler in feedbackRoutes.js is taking
resolved_by from req.body and logging req.body; change it to derive the resolver
from the authenticated user (use req.user._id) and remove or restrict the
console.log of the full body to avoid sensitive data exposure. Specifically, in
the async handler where feedbackId, actions_taken, and resolved_by are read and
where you set feedback.resolved_by, stop reading resolved_by from req.body and
instead set feedback.resolved_by = req.user._id (ensure req.user exists and
return 401 if not authenticated); also remove the console.log(req.body) (or
replace with a scoped debug log that excludes sensitive fields) and keep the
check for actions_taken as-is.

In `@backend/routes/profile.js`:
- Line 87: Replace the incorrect reference to the uninitialized local binding by
calling the imported model: change the call using user.findOne(...) to
User.findOne(...), and ensure you don't shadow the imported User with a local
variable named user (e.g., keep const user = await User.findOne({ user_id: ID_No
}); so the imported symbol User is used to query and the local variable user
holds the result).

In `@backend/utils/batchValidate.js`:
- Line 3: zodObjectId currently uses a regex that allows non-hex characters;
update the pattern used in the zodObjectId constant (zodObjectId) to only allow
24 hex characters by replacing the character class with the correct hex range
and keep the same error message or adjust it to "Invalid ObjectId" — i.e., use a
regex that enforces /^[0-9a-fA-F]{24}$/ so validations only accept valid MongoDB
ObjectId hex strings.
🟡 Minor comments (5)
backend/middlewares/isAuthenticated.js-29-36 (1)

29-36: ⚠️ Potential issue | 🟡 Minor

jwtIsAuthenticated does not propagate the error reason on token verification failure.

The catch block swallows the actual JWT error (e.g., TokenExpiredError vs JsonWebTokenError). Consider logging err.name or err.message at debug level for observability.

Also, note that req.user is set to the raw decoded payload. If downstream code expects a Mongoose document (e.g., req.user._id as an ObjectId), this could cause subtle mismatches since jwt.verify returns a plain object with string fields.

backend/utils/batchValidate.js-6-6 (1)

6-6: ⚠️ Potential issue | 🟡 Minor

Misleading error message for min(5).

min(5) enforces a minimum length of 5, but the message says "Title is required," which better suits an emptiness check. Consider something like "Title must be at least 5 characters".

backend/utils/batchValidate.js-10-10 (1)

10-10: ⚠️ Potential issue | 🟡 Minor

Typo: "Atleast" → "At least".

✏️ Fix
-  users: zod.array(zodObjectId).min(1, "Atleast 1 user must be associated."),
+  users: zod.array(zodObjectId).min(1, "At least 1 user must be associated."),
backend/models/organizationSchema.js-15-19 (1)

15-19: ⚠️ Potential issue | 🟡 Minor

Inconsistent casing in type enum values.

"Council", "Club", "Committee" use PascalCase while "independent_position" uses snake_case. This can cause subtle bugs when comparing/filtering by type. Consider normalizing to a single convention.

backend/controllers/certificateController.js-15-15 (1)

15-15: ⚠️ Potential issue | 🟡 Minor

Typo: "messge""message".

Proposed fix
-    return res.status(404).json({ messge: "Invalid data (User not found)" });
+    return res.status(404).json({ message: "Invalid data (User not found)" });
🧹 Nitpick comments (8)
backend/middlewares/isAuthenticated.js (1)

14-22: Remove commented-out code.

There are three blocks of dead commented-out code: the Bearer token alternative (Lines 15–22), the debug log (Line 32), and a large block of leftover controller logic (Lines 44–84). These add noise and should be removed — version control preserves history.

Also applies to: 32-32, 44-84

backend/routes/events.js (1)

442-465: Verbose debug logging should not ship to production.

Lines 442–465 log the full event object (before and after update) with JSON.stringify. This can leak sensitive data to logs and degrades performance on large documents. These appear to be temporary debug aids — consider removing them or gating behind a debug flag/log level.

package.json (1)

1-6: Remove unused dependencies from root package.json.

The root package.json contains only passport-local and passport-local-mongoose, which are unused. There are no root-level JavaScript files, no monorepo workspaces, and no scripts that reference these packages. Since the backend uses JWT/bcrypt for auth (not Passport-Local), these dependencies are unnecessary and should be removed. Either delete the root package.json entirely if it serves no other purpose, or confirm its intended use if this is part of a planned monorepo setup.

backend/utils/authValidate.js (1)

3-6: Extract shared email regex to a constant to reduce duplication.

The IIT Bhilai email regex is duplicated across loginValidate and registerValidate. Extract it to a shared constant.

Proposed refactor
 const zod = require("zod");
+
+const iitbhilaiEmailRegex = /^[a-zA-Z0-9._%+-]+@iitbhilai\.ac\.in$/i;
 
 const loginValidate = zod.object({
-  username: zod.string().regex(/^[a-zA-Z0-9._%+-]+@iitbhilai\.ac\.in$/i),
+  username: zod.string().regex(iitbhilaiEmailRegex),
   password: zod.string().min(8),
 });
 
 const registerValidate = zod.object({
-  username: zod.string().regex(/^[a-zA-Z0-9._%+-]+@iitbhilai\.ac\.in$/i),
+  username: zod.string().regex(iitbhilaiEmailRegex),
   password: zod.string().min(8),

Also applies to: 8-10

backend/package.json (1)

56-57: nodemon and npm should not be runtime dependencies.

nodemon (line 56) is a dev tool and belongs in devDependencies. npm (line 57) is the package manager itself and generally should not be listed as a dependency at all. These inflate the production install unnecessarily.

Proposed fix
     "morgan": "^1.10.0",
     "multer": "^2.0.1",
     "nodemailer": "^7.0.3",
-    "nodemon": "^3.1.10",
-    "npm": "^10.2.0",
     "passport": "^0.7.0",

Then add nodemon under devDependencies:

   "devDependencies": {
     "cookie": "^0.5.0",
     "eslint": "^8.56.0",
     "eslint-plugin-node": "^11.1.0",
     "husky": "^8.0.3",
     "lint-staged": "^15.2.0",
+    "nodemon": "^3.1.10",
     "prettier": "^3.1.1"
   }
backend/routes/announcements.js (1)

226-226: Manual updatedAt assignment is redundant — schema uses timestamps: true.

The announcementSchema in schema.js (line 104) now includes { timestamps: true }, which means Mongoose automatically updates updatedAt on every .save(). This manual assignment is unnecessary and could interfere with Mongoose's timestamp management.

♻️ Proposed fix
-      announcement.updatedAt = Date.now();
       await announcement.save();
backend/controllers/certificateController.js (2)

11-11: Remove debug console.log statements before merging.

There are ~8 console.log calls (some commented out) that leak internal IDs to stdout. These should be removed or replaced with a proper logger at debug level.

Also applies to: 45-45, 68-68, 76-76, 89-89, 100-100, 106-106, 109-109


147-147: Return 201 Created for successful resource creation.

res.json(...) defaults to status 200. Since a new batch is being created, res.status(201).json(...) is more semantically correct.

Comment on lines +10 to +148
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" });
}

//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" });
}

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" });
}

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 };
}),
);

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 });
}
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 | 🟠 Major

No try/catch — any database error will crash the request as an unhandled rejection.

Wrap the entire handler body in a try/catch that returns a 500 response, or use an async error-handling wrapper middleware.

🤖 Prompt for AI Agents
In `@backend/controllers/certificateController.js` around lines 10 - 148, The
createBatch handler currently has no try/catch so any DB or runtime error will
crash the request; wrap the entire async body of createBatch (all awaits like
User.findById, PositionHolder.findOne, OrganizationalUnit.findById, Promise.all
user checks, CertificateBatch.create, etc.) in a try/catch (or use your existing
async error-wrapper middleware) and in the catch log the error and return
res.status(500).json({ message: "Internal server error", error: /* brief error
message or id */ }) to ensure unhandled rejections are converted to proper HTTP
500 responses.

Comment on lines +18 to +20
if (user.role && user.role !== "CLUB_COORDINATOR") {
return res.status(403).json({ message: "Not authorized to perform the task" });
}
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

Authorization check allows users with no role.

user.role && user.role !== "CLUB_COORDINATOR" evaluates to false when user.role is undefined/null, so users without any role bypass this guard. The intent appears to be the opposite — only CLUB_COORDINATOR should be allowed.

Proposed fix
-  if (user.role && user.role !== "CLUB_COORDINATOR") {
+  if (user.role !== "CLUB_COORDINATOR") {
     return res.status(403).json({ message: "Not authorized to perform the task" });
   }
📝 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
if (user.role && user.role !== "CLUB_COORDINATOR") {
return res.status(403).json({ message: "Not authorized to perform the task" });
}
if (user.role !== "CLUB_COORDINATOR") {
return res.status(403).json({ message: "Not authorized to perform the task" });
}
🤖 Prompt for AI Agents
In `@backend/controllers/certificateController.js` around lines 18 - 20, The
authorization guard currently lets users with no role pass because it checks
user.role && user.role !== "CLUB_COORDINATOR"; change it to explicitly deny when
role is missing or not the coordinator, e.g. replace the condition with if
(!user.role || user.role !== "CLUB_COORDINATOR") and keep the existing response
using res.status(403).json({ message: "Not authorized to perform the task" });
so only users whose user.role === "CLUB_COORDINATOR" are allowed.

Comment on lines +44 to +48
const position = await Position.findById(positionHolder.position_id);
console.log(position._id);
if (!position) {
return res.status(403).json({ message: "Your position is invalid" });
}
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 accesses position._id via console.log, but the null check is on line 46. If findById returns null, this will throw 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" });
   }
🤖 Prompt for AI Agents
In `@backend/controllers/certificateController.js` around lines 44 - 48, The code
logs position._id before verifying the result of Position.findById, which can
cause a TypeError if position is null; update the handler in
certificateController.js so you first check the returned value from
Position.findById(positionHolder.position_id) (i.e., if (!position) return
res.status(403).json(...)) and only after that call console.log(position._id) or
otherwise safely access properties of position.

Comment on lines +88 to +112
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" });
}
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

Multiple null-pointer crashes in approver resolution.

Several values are dereferenced before their null checks:

  1. Line 89: gensecObj._id is accessed, but the null check for gensecObj is at line 110.
  2. Line 102-105: presidentHolder is never null-checked; if no holder exists, line 105 crashes.
  3. Line 109: presidentObj._id is accessed before the null check at line 110.

Move null checks immediately after each query, and remove the console.log statements that access potentially null objects.

Proposed fix (sketch)
   const gensecObj = await User.findOne({ role: `GENSEC_${category}` });
-  console.log(gensecObj._id);
+  if (!gensecObj) {
+    return res.status(500).json({ message: "General Secretary not found" });
+  }
 
   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,
   });
+  if (!presidentHolder) {
+    return res.status(500).json({ message: "President holder not found" });
+  }
   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" });
+  if (!presidentObj) {
+    return res.status(500).json({ message: "President user not found" });
   }
📝 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 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" });
}
const gensecObj = await User.findOne({ role: `GENSEC_${category}` });
if (!gensecObj) {
return res.status(500).json({ message: "General Secretary not found" });
}
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" });
}
const presidentHolder = await PositionHolder.findOne({
position_id: presidentPosition._id,
});
if (!presidentHolder) {
return res.status(500).json({ message: "President holder not found" });
}
const presidentId = presidentHolder.user_id.toString();
const presidentObj = await User.findById(presidentId);
if (!presidentObj) {
return res.status(500).json({ message: "President user not found" });
}
🤖 Prompt for AI Agents
In `@backend/controllers/certificateController.js` around lines 88 - 112, The code
dereferences query results before null-checking, causing crashes: after calling
User.findOne for gensecObj, Position.findOne for presidentPosition,
PositionHolder.findOne for presidentHolder, and User.findById for presidentObj,
add immediate null/undefined checks and return the 500 response if any are
missing (use the same error messages), and remove or move any console.log calls
that access properties like ._id or .user_id until after those checks;
specifically update the logic around gensecObj, presidentPosition,
presidentHolder, and presidentObj to validate each result right after its query
(and only then access ._id or .user_id).

Comment on lines +116 to +128
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 };
}),
);
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

Validation check is always truthy — invalid IDs are never caught.

zodObjectId.safeParse(uid) returns a result object (e.g. { success: true/false, ... }), which is always truthy. Line 119 should check !validation.success instead of !validation.

Proposed fix
     const validation = zodObjectId.safeParse(uid);
-    if (!validation) {
+    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
In `@backend/controllers/certificateController.js` around lines 116 - 128, The
validation logic inside the users.map is wrong because
zodObjectId.safeParse(uid) returns an object, so change the check to inspect
validation.success; in the block that builds userChecks (where users.map calls
zodObjectId.safeParse), replace the condition that uses !validation with
!validation.success so invalid IDs return { uid, ok: false, reason: "Invalid ID"
} and subsequent lookups in User.findById only run for successful validations.

const isAuthenticated= require("../middlewares/isAuthenticated");

const User = require("../models/userSchema");
const isAuthenticated = require("../middlewares/isAuthenticated");
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

Critical: Import mismatch — isAuthenticated will be the exports object, not the middleware function.

The middleware module now exports { isAuthenticated, jwtIsAuthenticated }. Every other route file in this PR uses destructured import const { isAuthenticated } = require(...). This file uses a default-style import, so isAuthenticated here receives the entire exports object. When Express invokes it as middleware, it won't be a function, causing a runtime error on the /fetchAuth route (line 25).

🐛 Proposed fix
-const isAuthenticated = require("../middlewares/isAuthenticated");
+const { isAuthenticated } = require("../middlewares/isAuthenticated");
🤖 Prompt for AI Agents
In `@backend/routes/auth.js` at line 13, The import in backend/routes/auth.js is
grabbing the whole exports object instead of the middleware function; replace
the default-style require with a destructured import so isAuthenticated refers
to the function exported by the module (e.g., change the require to const {
isAuthenticated } = require("../middlewares/isAuthenticated")); ensure any other
references to jwtIsAuthenticated are similarly imported via destructuring if
needed so Express receives actual middleware functions when mounting routes like
/fetchAuth.

status: user.status,
};

const token = jwt.sign(payload, secretKey, { expiresIn: "30m" });
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

Critical: secretKey is not defined — jwt.sign will throw a ReferenceError.

There is no secretKey variable declared or imported anywhere in this file. This will crash the login route at runtime. You likely need to read it from an environment variable.

🐛 Proposed fix — add a declaration near the top of the file (e.g., after line 15)
 const bcrypt = require("bcrypt");
+
+const secretKey = process.env.JWT_SECRET_TOKEN;
📝 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 token = jwt.sign(payload, secretKey, { expiresIn: "30m" });
const bcrypt = require("bcrypt");
const secretKey = process.env.JWT_SECRET_TOKEN;
🤖 Prompt for AI Agents
In `@backend/routes/auth.js` at line 64, The jwt.sign call uses an undefined
secretKey which will throw a ReferenceError at runtime; declare and read the
secret from an environment variable (e.g., process.env.JWT_SECRET) near the top
of the file and use that value for secretKey in the token creation (update the
jwt.sign call that uses secretKey). Also add a small guard in the auth
initialization (or before issuing tokens) to throw/log a clear error if the JWT
secret is missing so the login route fails fast with a helpful message.

Comment on lines +188 to +211
async (req, res) => {
const feedbackId = req.params.id;
const { actions_taken, resolved_by } = req.body;
console.log(req.body);
console.log("User resolving feedback:", resolved_by);

if (!actions_taken || actions_taken.trim() === "") {
return res.status(400).json({ error: "Resolution comment is required." });
}

if (feedback.is_resolved) {
return res.status(400).json({ error: "Feedback is already resolved." });
}
try {
const feedback = await Feedback.findById(feedbackId);
if (!feedback) {
return res.status(404).json({ error: "Feedback not found" });
}

feedback.is_resolved = true;
feedback.resolved_at = new Date();
feedback.actions_taken = actions_taken;
feedback.resolved_by = resolved_by;
if (feedback.is_resolved) {
return res.status(400).json({ error: "Feedback is already resolved." });
}

await feedback.save();
feedback.is_resolved = true;
feedback.resolved_at = new Date();
feedback.actions_taken = actions_taken;
feedback.resolved_by = resolved_by;
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 | 🟠 Major

resolved_by should come from the authenticated user, not from req.body.

Taking resolved_by from the request body (line 190, 211) allows a client to impersonate any user as the resolver. Since this route already requires isAuthenticated, use req.user._id instead.

Additionally, console.log(req.body) on line 191 may log sensitive data — remove or scope down.

Proposed fix
   async (req, res) => {
     const feedbackId = req.params.id;
-    const { actions_taken, resolved_by } = req.body;
-    console.log(req.body);
-    console.log("User resolving feedback:", resolved_by);
+    const { actions_taken } = req.body;
 
     if (!actions_taken || actions_taken.trim() === "") {
       return res.status(400).json({ error: "Resolution comment is required." });
     }
 
     try {
       const feedback = await Feedback.findById(feedbackId);
       if (!feedback) {
         return res.status(404).json({ error: "Feedback not found" });
       }
 
       if (feedback.is_resolved) {
         return res.status(400).json({ error: "Feedback is already resolved." });
       }
 
       feedback.is_resolved = true;
       feedback.resolved_at = new Date();
       feedback.actions_taken = actions_taken;
-      feedback.resolved_by = resolved_by;
+      feedback.resolved_by = req.user._id;
🤖 Prompt for AI Agents
In `@backend/routes/feedbackRoutes.js` around lines 188 - 211, The route handler
in feedbackRoutes.js is taking resolved_by from req.body and logging req.body;
change it to derive the resolver from the authenticated user (use req.user._id)
and remove or restrict the console.log of the full body to avoid sensitive data
exposure. Specifically, in the async handler where feedbackId, actions_taken,
and resolved_by are read and where you set feedback.resolved_by, stop reading
resolved_by from req.body and instead set feedback.resolved_by = req.user._id
(ensure req.user exists and return 401 if not authenticated); also remove the
console.log(req.body) (or replace with a scoped debug log that excludes
sensitive fields) and keep the check for actions_taken as-is.

return res.status(400).json({ error: "ID_No is required" });
}

const user = await user.findOne({ user_id: ID_No });
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

Critical: user.findOne should be User.findOne — variable used before its own declaration.

Line 87 declares const user and simultaneously calls user.findOne(...), which references the not-yet-initialized binding. This will throw a ReferenceError at runtime due to the temporal dead zone. The imported model on line 7 is User (capital U).

🐛 Proposed fix
-    const user = await user.findOne({ user_id: ID_No });
+    const user = await User.findOne({ user_id: ID_No });
📝 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 user = await user.findOne({ user_id: ID_No });
const user = await User.findOne({ user_id: ID_No });
🧰 Tools
🪛 Biome (2.3.13)

[error] 87-87: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

🤖 Prompt for AI Agents
In `@backend/routes/profile.js` at line 87, Replace the incorrect reference to the
uninitialized local binding by calling the imported model: change the call using
user.findOne(...) to User.findOne(...), and ensure you don't shadow the imported
User with a local variable named user (e.g., keep const user = await
User.findOne({ user_id: ID_No }); so the imported symbol User is used to query
and the local variable user holds the result).

@@ -0,0 +1,16 @@
const zod = require("zod");

const zodObjectId = zod.string().regex(/^[0-9a-zA-Z]{24}$/, "Invalid ObjectId");
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

ObjectId regex accepts invalid characters.

MongoDB ObjectIds are 24-character hex strings. The current regex [0-9a-zA-Z] allows non-hex characters (e.g., GZ, gz), which would pass validation but fail on database lookup.

🐛 Proposed fix
-const zodObjectId = zod.string().regex(/^[0-9a-zA-Z]{24}$/, "Invalid ObjectId");
+const zodObjectId = zod.string().regex(/^[0-9a-fA-F]{24}$/, "Invalid ObjectId");
📝 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 zodObjectId = zod.string().regex(/^[0-9a-zA-Z]{24}$/, "Invalid ObjectId");
const zodObjectId = zod.string().regex(/^[0-9a-fA-F]{24}$/, "Invalid ObjectId");
🤖 Prompt for AI Agents
In `@backend/utils/batchValidate.js` at line 3, zodObjectId currently uses a regex
that allows non-hex characters; update the pattern used in the zodObjectId
constant (zodObjectId) to only allow 24 hex characters by replacing the
character class with the correct hex range and keep the same error message or
adjust it to "Invalid ObjectId" — i.e., use a regex that enforces
/^[0-9a-fA-F]{24}$/ so validations only accept valid MongoDB ObjectId hex
strings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant