-
Notifications
You must be signed in to change notification settings - Fork 56
Refactor/login register #222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 13 commits
cbefec9
78dbc24
f2e5883
ae0295f
57c4189
61dfd89
29bc583
a8b4d8e
82d3b70
bde7d5e
ecc1ebd
8126097
4e96a8e
d3c0261
2a31781
53d7216
cdf07e2
c342d2b
649fb09
3fe6ed8
f521062
2ef2e05
0f47b6a
0bd1220
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| const passport = require("passport"); | ||
| const GoogleStrategy = require("passport-google-oauth20").Strategy; | ||
| const LocalStrategy = require("passport-local").Strategy; | ||
| const isIITBhilaiEmail = require("../utils/isIITBhilaiEmail"); | ||
| const User = require("../models/userSchema"); | ||
| const { loginValidate } = require("../utils/authValidate"); | ||
| const bcrypt = require("bcrypt"); | ||
| // Google OAuth Strategy | ||
| passport.use( | ||
| new GoogleStrategy( | ||
| { | ||
| clientID: process.env.GOOGLE_CLIENT_ID, | ||
| clientSecret: process.env.GOOGLE_CLIENT_SECRET, | ||
| callbackURL: `${process.env.BACKEND_URL}/auth/google/verify`, // Update with your callback URL | ||
| }, | ||
| async (accessToken, refreshToken, profile, done) => { | ||
| // Check if the user already exists in your database | ||
| const email = profile.emails?.[0]?.value; | ||
| if (!email) { | ||
| return done(null, false, { message: "Email not available from Google." }); | ||
| } | ||
| if (!isIITBhilaiEmail(profile.emails[0].value)) { | ||
| console.log("Google OAuth blocked for: ", profile.emails[0].value); | ||
| return done(null, false, { | ||
| message: "Only @iitbhilai.ac.in emails are allowed.", | ||
| }); | ||
| } | ||
| try { | ||
| const user = await User.findOne({ username: email }); | ||
|
|
||
| if (user) { | ||
| // If user exists, return the user | ||
| return done(null, user); | ||
| } | ||
| // If user doesn't exist, create a new user in your database | ||
| const newUser = await User.create({ | ||
| username: email, | ||
| role: "STUDENT", | ||
| strategy: "google", | ||
| personal_info: { | ||
| name: profile.displayName || "No Name", | ||
| email: email, | ||
| profilePic: | ||
| profile.photos && profile.photos.length > 0 | ||
| ? profile.photos[0].value | ||
| : "https://www.gravatar.com/avatar/?d=mp", | ||
| }, | ||
| onboardingComplete: false, | ||
| }); | ||
|
|
||
| return done(null, newUser); | ||
| } catch (error) { | ||
| return done(error); | ||
| } | ||
| }, | ||
| ), | ||
| ); | ||
|
|
||
| //Local Strategy | ||
| passport.use(new LocalStrategy(async (username, password, done) => { | ||
|
|
||
| const result = loginValidate.safeParse({ username, password }); | ||
|
|
||
| if (!result.success) { | ||
| let errors = result.error.issues.map((issue) => issue.message); | ||
| return done(null, false, {message: errors}); | ||
| } | ||
|
|
||
| try{ | ||
|
|
||
| const user = await User.findOne({ username }); | ||
| if (!user) { | ||
| return done(null, false, {message: "Invalid user credentials"}); | ||
| } | ||
|
|
||
|
|
||
| if (user.strategy !== "local" || !user.password) { | ||
| return done(null, false, { message: "Invalid login method" }); | ||
| } | ||
|
|
||
| const isValid = await bcrypt.compare(password, user.password); | ||
| if (!isValid) { | ||
| return done(null, false, { message: "Invalid user credentials" }); | ||
| } | ||
|
|
||
|
|
||
| return done(null, user); | ||
| }catch(err){ | ||
| return done(err); | ||
| } | ||
|
|
||
| })); | ||
|
|
||
|
|
||
| //When login succeeds this will run | ||
| // serialize basically converts user obj into a format that can be transmitted(like a string, etc...) | ||
| // here take user obj and done callback and store only userId in session | ||
| passport.serializeUser((user, done) => { | ||
| done(null, user._id.toString()); | ||
| }); | ||
|
|
||
| //When a request comes in, take the stored id, fetch full user from DB, and attach it to req.user. | ||
| passport.deserializeUser(async (id, done) => { | ||
| try { | ||
| let user = await User.findById(id); | ||
| if(!user) return done(null, false); | ||
| done(null, user); | ||
| } catch (err) { | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| done(err, null); | ||
| } | ||
| }); | ||
|
|
||
| module.exports = passport; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| const { | ||
| User, | ||
| PositionHolder, | ||
| Position, | ||
| OrganizationalUnit, | ||
| } = require("../models/schema"); | ||
| const { CertificateBatch } = require("../models/certificateSchema"); | ||
| const { validateBatchSchema, zodObjectId } = require("../utils/batchValidate"); | ||
|
|
||
| async function createBatch(req, res) { | ||
| //console.log(req.user); | ||
| const id = req.user.id; | ||
| const user = await User.findById(id); | ||
| if (!user) { | ||
| return res.status(404).json({ messge: "Invalid data (User not found)" }); | ||
| } | ||
|
|
||
| if (user.role && user.role !== "CLUB_COORDINATOR") { | ||
| return res.status(403).json({ message: "Not authorized to perform the task" }); | ||
| } | ||
|
codewkaushik404 marked this conversation as resolved.
Outdated
|
||
|
|
||
| //to get user club | ||
| // positionHolders({user_id: id}) -> positions({_id: position_id}) -> organizationalUnit({_id: unit_id}) -> unit_id = "Club name" | ||
| const { title, unit_id, commonData, template_id, users } = req.body; | ||
| const validation = validateBatchSchema.safeParse({ | ||
| title, | ||
| unit_id, | ||
| commonData, | ||
| template_id, | ||
| users, | ||
| }); | ||
|
|
||
| if (!validation.success) { | ||
| let errors = validation.error.issues.map(issue => issue.message); | ||
| return res.status(400).json({ message: errors }); | ||
| } | ||
|
|
||
| // Get coordinator's position and unit | ||
| const positionHolder = await PositionHolder.findOne({ user_id: id }); | ||
| if (!positionHolder) { | ||
| return res.status(403).json({ message: "You are not part of any position in a unit" }); | ||
| } | ||
|
|
||
| const position = await Position.findById(positionHolder.position_id); | ||
| console.log(position._id); | ||
| if (!position) { | ||
| return res.status(403).json({ message: "Your position is invalid" }); | ||
| } | ||
|
codewkaushik404 marked this conversation as resolved.
Outdated
|
||
|
|
||
| const userUnitId = position.unit_id.toString(); | ||
| if (userUnitId !== unit_id) { | ||
| return res | ||
| .status(403) | ||
| .json({ | ||
| message: | ||
| "You are not authorized to initiate batches outside of your club", | ||
| }); | ||
| } | ||
|
|
||
| //const clubId = unit_id; | ||
| // Ensure unit_id is a Club | ||
| const unitObj = await OrganizationalUnit.findById(unit_id); | ||
| if (!unitObj || unitObj.type !== "Club") { | ||
| return res | ||
| .status(403) | ||
| .json({ message: "Invalid Data: unit is not a Club" }); | ||
| } | ||
| console.log(unitObj._id); | ||
|
|
||
| // Get council (parent unit) and ensure it's a Council | ||
| if (!unitObj.parent_unit_id) { | ||
| return res | ||
| .status(403) | ||
| .json({ message: "Invalid Data: club does not belong to a council" }); | ||
| } | ||
| console.log(unitObj.parent_unit_id); | ||
|
|
||
| const councilObj = await OrganizationalUnit.findById(unitObj.parent_unit_id); | ||
| if (!councilObj || councilObj.type !== "Council") { | ||
| return res.status(403).json({ message: "Invalid Data: council not found" }); | ||
| } | ||
|
|
||
| //const councilId = councilObj._id.toString(); | ||
| const presidentOrgUnitId = councilObj.parent_unit_id; | ||
| const category = councilObj.category.toUpperCase(); | ||
|
|
||
| // Resolve General Secretary and President for the council (server-side, tamper-proof) | ||
| const gensecObj = await User.findOne({ role: `GENSEC_${category}` }); | ||
| console.log(gensecObj._id); | ||
|
|
||
| const presidentPosition = await Position.findOne({ | ||
| unit_id: presidentOrgUnitId, | ||
| title: /president/i, | ||
| }); | ||
| if (!presidentPosition) { | ||
| return res | ||
| .status(500) | ||
| .json({ message: "President position not found for council" }); | ||
| } | ||
| console.log(presidentPosition._id); | ||
|
|
||
| const presidentHolder = await PositionHolder.findOne({ | ||
| position_id: presidentPosition._id, | ||
| }); | ||
| const presidentId = presidentHolder.user_id.toString(); | ||
| console.log(presidentId); | ||
| const presidentObj = await User.findById(presidentId); | ||
|
|
||
| console.log(presidentObj._id); | ||
| if (!gensecObj || !presidentObj) { | ||
| return res.status(500).json({ message: "Approvers not found" }); | ||
| } | ||
|
codewkaushik404 marked this conversation as resolved.
Outdated
|
||
|
|
||
| const approverIds = [gensecObj._id.toString(), presidentId]; | ||
|
|
||
| const userChecks = await Promise.all( | ||
| users.map(async (uid) => { | ||
| const validation = zodObjectId.safeParse(uid); | ||
| if (!validation) { | ||
| return { uid, ok: false, reason: "Invalid ID" }; | ||
| } | ||
|
|
||
| const userObj = await User.findById(uid); | ||
| if (!userObj) return { uid, ok: false, reason: "User not found" }; | ||
|
|
||
| return { uid, ok: true }; | ||
| }), | ||
| ); | ||
|
codewkaushik404 marked this conversation as resolved.
Outdated
|
||
|
|
||
| const invalidData = userChecks.filter((c) => !c.ok); | ||
| if (invalidData.length > 0) { | ||
| return res | ||
| .status(400) | ||
| .json({ message: "Invalid user data sent", details: invalidData }); | ||
| } | ||
|
|
||
| const newBatch = await CertificateBatch.create({ | ||
| title, | ||
| unit_id, | ||
| commonData, | ||
| templateId: template_id, | ||
| initiatedBy: id, | ||
| approverIds, | ||
| users, | ||
| }); | ||
|
|
||
| res.json({ message: "New Batch created successfully", details: newBatch }); | ||
| } | ||
|
codewkaushik404 marked this conversation as resolved.
|
||
|
|
||
| module.exports = { | ||
| createBatch, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,13 +1,14 @@ | ||||||||||||||||||||||
| const express = require("express"); | ||||||||||||||||||||||
| require("dotenv").config(); | ||||||||||||||||||||||
| // eslint-disable-next-line node/no-unpublished-require | ||||||||||||||||||||||
| const { connectDB } = require("./config/db.js"); | ||||||||||||||||||||||
| const MongoStore = require("connect-mongo"); | ||||||||||||||||||||||
| const cookieParser = require("cookie-parser"); | ||||||||||||||||||||||
| const cors = require("cors"); | ||||||||||||||||||||||
| const routes_auth = require("./routes/auth"); | ||||||||||||||||||||||
| const routes_general = require("./routes/route"); | ||||||||||||||||||||||
| const session = require("express-session"); | ||||||||||||||||||||||
| const bodyParser = require("body-parser"); | ||||||||||||||||||||||
| const { connectDB } = require("./db"); | ||||||||||||||||||||||
| const myPassport = require("./models/passportConfig"); // Adjust the path accordingly | ||||||||||||||||||||||
| const myPassport = require("./config/passportConfig.js"); // Adjust the path accordingly | ||||||||||||||||||||||
| const onboardingRoutes = require("./routes/onboarding.js"); | ||||||||||||||||||||||
| const profileRoutes = require("./routes/profile.js"); | ||||||||||||||||||||||
| const feedbackRoutes = require("./routes/feedbackRoutes.js"); | ||||||||||||||||||||||
|
|
@@ -18,8 +19,8 @@ const positionsRoutes = require("./routes/positionRoutes.js"); | |||||||||||||||||||||
| const organizationalUnitRoutes = require("./routes/orgUnit.js"); | ||||||||||||||||||||||
| const announcementRoutes = require("./routes/announcements.js"); | ||||||||||||||||||||||
| const dashboardRoutes = require("./routes/dashboard.js"); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const analyticsRoutes = require("./routes/analytics.js"); | ||||||||||||||||||||||
| const certificateRoutes = require("./routes/certificateRoutes.js"); | ||||||||||||||||||||||
| const app = express(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (process.env.NODE_ENV === "production") { | ||||||||||||||||||||||
|
|
@@ -28,23 +29,32 @@ if (process.env.NODE_ENV === "production") { | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| app.use(cors({ origin: process.env.FRONTEND_URL, credentials: true })); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Connect to MongoDB | ||||||||||||||||||||||
| connectDB(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| app.use(bodyParser.json()); | ||||||||||||||||||||||
| app.use(cookieParser()); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| //Replaced bodyParser with express.json() - the new standard | ||||||||||||||||||||||
| app.use(express.json()); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| app.use( | ||||||||||||||||||||||
| session({ | ||||||||||||||||||||||
| secret: "keyboard cat", | ||||||||||||||||||||||
| secret: process.env.SESSION_SECRET, | ||||||||||||||||||||||
| resave: false, | ||||||||||||||||||||||
| saveUninitialized: false, | ||||||||||||||||||||||
| cookie: { | ||||||||||||||||||||||
| secure: process.env.NODE_ENV === "production", // HTTPS only in prod | ||||||||||||||||||||||
| sameSite: process.env.NODE_ENV === "production" ? "none" : "lax", // cross-origin in prod | ||||||||||||||||||||||
| maxAge: 60*60*1000 | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| store: MongoStore.create({ | ||||||||||||||||||||||
| mongoUrl: process.env.MONGODB_URI, | ||||||||||||||||||||||
| ttl: 60*60*1000, | ||||||||||||||||||||||
| collectionName: "sessions" | ||||||||||||||||||||||
| }), | ||||||||||||||||||||||
|
Comment on lines
+48
to
+52
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n backend/index.js | sed -n '40,60p'Repository: OpenLake/Student_Database_COSA Length of output: 825 🌐 Web query:
💡 Result: In connect-mongo, the
The connect-mongo Proposed fix store: MongoStore.create({
mongoUrl: process.env.MONGODB_URI,
- ttl: 60*60*1000,
+ ttl: 60 * 60, // 3600 seconds = 1 hour (matches cookie maxAge)
collectionName: "sessions"
}),📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| name: "token" | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# First, let's check the structure of the backend directory
echo "=== Backend directory structure ==="
find backend -type f -name "*.js" | head -20Repository: OpenLake/Student_Database_COSA Length of output: 797 🏁 Script executed: #!/bin/bash
# Run the provided verification script
rg -n "cookies\|token\|req\.cookies" backend/middlewares/isAuthenticated.js -A2 -B2Repository: OpenLake/Student_Database_COSA Length of output: 56 🏁 Script executed: #!/bin/bash
# Check the context around line 53 in backend/index.js
echo "=== Context around line 53 in backend/index.js ==="
sed -n '40,70p' backend/index.jsRepository: OpenLake/Student_Database_COSA Length of output: 1025 🏁 Script executed: #!/bin/bash
# Search for where jwtIsAuthenticated is used
echo "=== Usage of jwtIsAuthenticated ==="
rg "jwtIsAuthenticated" backend -n -A1 -B1Repository: OpenLake/Student_Database_COSA Length of output: 789 🏁 Script executed: #!/bin/bash
# Read the entire isAuthenticated.js file to see the implementation
cat -n backend/middlewares/isAuthenticated.jsRepository: OpenLake/Student_Database_COSA Length of output: 2903 🏁 Script executed: #!/bin/bash
# Check the certificate routes to understand the flow
cat -n backend/routes/certificateRoutes.jsRepository: OpenLake/Student_Database_COSA Length of output: 399 Session cookie named The session middleware is configured with Rename the session cookie to avoid the collision: 🐛 Proposed fix- name: "token"
+ name: "sid"📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| }), | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| //Needed to initialize passport and all helper methods to req object | ||||||||||||||||||||||
| app.use(myPassport.initialize()); | ||||||||||||||||||||||
| app.use(myPassport.session()); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -67,8 +77,14 @@ app.use("/api/announcements", announcementRoutes); | |||||||||||||||||||||
| app.use("/api/dashboard", dashboardRoutes); | ||||||||||||||||||||||
| app.use("/api/announcements", announcementRoutes); | ||||||||||||||||||||||
| app.use("/api/analytics", analyticsRoutes); | ||||||||||||||||||||||
| app.use("/api/certificate-batches", certificateRoutes); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Start the server | ||||||||||||||||||||||
| app.listen(process.env.PORT || 8000, () => { | ||||||||||||||||||||||
| console.log(`connected to port ${process.env.PORT || 8000}`); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| (async function(){ | ||||||||||||||||||||||
| // Connect to MongoDB | ||||||||||||||||||||||
| await connectDB(); | ||||||||||||||||||||||
| app.listen(process.env.PORT || 5000, () => { | ||||||||||||||||||||||
| console.log(`connected to port ${process.env.PORT || 5000}`); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| })(); | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: OpenLake/Student_Database_COSA
Length of output: 548
LocalStrategy enforces IIT Bhilai email-only access; existing non-IIT users will be blocked.
The
loginValidateschema inbackend/utils/authValidate.jsenforces a strict regex pattern (^[a-zA-Z0-9._%+-]+@iitbhilai\.ac\.in$) that rejects any username lacking the@iitbhilai.ac.indomain. This means any existing user account not using an IIT Bhilai email will be permanently locked out. Before deploying this change, audit the database for non-IIT Bhilai usernames and have a migration strategy ready (or disable this validation if it's unintended).🤖 Prompt for AI Agents