Skip to content

feat: implement Google & GitHub OAuth and persist session stores via …#343

Open
Soumadip-Eagle123 wants to merge 2 commits into
GitMetricsLab:mainfrom
Soumadip-Eagle123:feature/social-auth-and-hardening
Open

feat: implement Google & GitHub OAuth and persist session stores via …#343
Soumadip-Eagle123 wants to merge 2 commits into
GitMetricsLab:mainfrom
Soumadip-Eagle123:feature/social-auth-and-hardening

Conversation

@Soumadip-Eagle123
Copy link
Copy Markdown

@Soumadip-Eagle123 Soumadip-Eagle123 commented May 20, 2026

…Mongo

Related Issue

  • Closes: # (Leave blank or add issue number if you have one, otherwise remove this line)

Description

This PR introduces secure social authentication via Passport.js and hardens the backend session architecture to support higher concurrent user loads without crashes.

Key changes include:

  • Google & GitHub OAuth: Integrated passport-google-oauth20 and passport-github2 strategies into the existing Passport ecosystem.
  • Model Compatibility Protection: Safeguarded the original user database schema by implementing a bypass check in the pre-save password-hashing hook for OAuth users, preventing unnecessary hashing of dummy external authentication tokens.
  • Production Session Management: Replaced the unsafe, memory-leaking default MemoryStore session implementation with connect-mongo to persist user sessions seamlessly inside MongoDB.
  • Frontend Integration: Added intuitive, high-performance sign-in buttons within the Login interface matching the design language of the platform.
  • Documentation: Updated .env.sample with the necessary blank configuration variables so future developers know how to set up social sign-on.

How Has This Been Tested?

  • Dependency Resolution: Fixed local/global package loading barriers by testing file structure execution.
  • Database Persistence: Confirmed that sessions are successfully writing and validating records inside the active MongoDB container collection.
  • Local Isolation: Verified that changes compile and run seamlessly without breaking the original local-auth flows.

Screenshots (if applicable)

(Optional: You can drag and drop an image of your updated Login screen here if you have one)


Type of Change

  • Bug fix
  • New feature
  • Code style update
  • Breaking change
  • Documentation update

Summary by CodeRabbit

Release Notes

  • New Features
    • Added OAuth authentication options—sign in and sign up using Google or GitHub accounts
    • Implemented MongoDB-based session persistence for improved session management
    • Updated environment configuration to support OAuth provider integration

Review Change Stack

@netlify
Copy link
Copy Markdown

netlify Bot commented May 20, 2026

Deploy Preview for github-spy ready!

Name Link
🔨 Latest commit 5b1ded9
🔍 Latest deploy log https://app.netlify.com/projects/github-spy/deploys/6a0dcc4615ec320008e6db9d
😎 Deploy Preview https://deploy-preview-343--github-spy.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

Warning

Rate limit exceeded

@Soumadip-Eagle123 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 37 minutes and 51 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9dff9046-9db8-45db-a128-1a2969274fd5

📥 Commits

Reviewing files that changed from the base of the PR and between d482433 and 5b1ded9.

📒 Files selected for processing (4)
  • backend/config/passportConfig.js
  • backend/models/User.js
  • backend/routes/auth.js
  • backend/server.js
📝 Walkthrough

Walkthrough

This PR integrates OAuth authentication for Google and GitHub into a full-stack application. The backend registers two passport strategies, configures MongoDB session persistence, adds OAuth routes, and updates the User model to handle external provider credentials. The frontend adds OAuth sign-in/sign-up buttons to existing login and signup pages.

Changes

OAuth Authentication Integration

Layer / File(s) Summary
Environment and Dependency Setup
backend/.env.sample, backend/package.json
MongoDB container host and OAuth credential placeholders are added to environment config; express, mongoose, connect-mongo, passport-google-oauth20, and passport-github2 dependencies are added or updated.
User Model OAuth Support
backend/models/User.js
Password hash is skipped for OAuth users via a sentinel value "OAUTH_USER_EXTERNAL_PROVIDER"; comparePassword returns false immediately for OAuth users instead of attempting bcrypt comparison.
OAuth Strategy Registration
backend/config/passportConfig.js
Google and GitHub strategies are registered with passport; each verifies users by provider email, derives usernames from profile data, creates new users with OAuth sentinel password, and returns session payload with id, username, and email.
MongoDB Session Persistence
backend/server.js
Express-session is configured with MongoStore to persist sessions in MongoDB, using MONGO_URI and setting cookie maxAge to 24 hours.
OAuth Route Endpoints
backend/routes/auth.js
OAuth provider routes initiate passport authentication with provider-specific scopes; callback routes complete authentication and redirect to the frontend URL on success or to login on failure.
Frontend OAuth UI
src/pages/Login/Login.tsx, src/pages/Signup/Signup.tsx
Login and Signup pages add "Continue with Google" and "Continue with GitHub" links that direct to the corresponding backend OAuth endpoints.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

level:intermediate, quality:clean

🐰 A rabbit hops through OAuth's garden fair,
Google and GitHub credentials in the air,
Sessions persist in MongoDB's deep nest,
Sign in with springs—the fastest way, the best!
🔐✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: implementing Google & GitHub OAuth authentication and persisting sessions via MongoDB.
Description check ✅ Passed The description is comprehensive and well-structured, covering all template sections with detailed explanations of changes, testing approach, and type of change indicators.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/config/passportConfig.js`:
- Around line 49-50: The current OAuth username construction using
profile.displayName + "_" + profile.id.substring(0,5) (see the username
assignment in passportConfig.js) can collide with existing users; change it to
produce a deterministic unique base that includes the provider to avoid
collisions (e.g. use provider + "_" + profile.id.substring(0,5) or provider +
"_" + profile.provider + "_" + id suffix) or implement a retry-on-duplicate-key
routine around the user creation logic (catch duplicate key errors when creating
the User and append/increment a suffix until creation succeeds); update both
places where username is built (the existing username assignment and the similar
block later around lines creating new OAuth users) and ensure any error handling
for duplicate-key (E11000) is covered so first OAuth login does not fail.
- Around line 44-50: The Google OAuth callback dereferences
profile.emails[0].value in the User.findOne lookup and when constructing a new
User (username and email) which will throw if profile.emails is missing; update
the Google strategy callback to safely extract the email (e.g., const email =
profile.emails?.[0]?.value) and if email is falsy call done(null, false, {
message: "No email provided by Google" }) to reject authentication, then use
that guarded email value in User.findOne and in the new User({ ... }) and keep
the username generation using profile.displayName but only if present.

In `@backend/models/User.js`:
- Around line 22-24: Replace the magic-string check this.password ===
"OAUTH_USER_EXTERNAL_PROVIDER" with explicit authProvider and providerId fields
on the User model: add authProvider (e.g. 'local' | 'oauth') and providerId,
update the model defaults/migration to include them, and change all logic that
currently branches on that password literal (e.g., the block in User.js that
returns early and the similar check at lines 34-36) to instead check
authProvider !== 'local' (or authProvider === 'oauth') to skip password logic;
also ensure user-creation and update paths set authProvider/providerId for
external users and that any password setters/validators (e.g., validatePassword,
setPassword) only operate when authProvider === 'local'.

In `@backend/routes/auth.js`:
- Around line 54-57: The OAuth callback handlers currently use
passport.authenticate("google", { failureRedirect: "/login" }) which points to a
non-existent backend path; update both passport.authenticate calls to build a
frontend-aware failure URL (e.g., failureRedirect: (process.env.FRONTEND_URL ||
"http://localhost:5173") + "/login") so failures are redirected to the frontend
login page, and keep the existing success res.redirect(process.env.FRONTEND_URL
|| "http://localhost:5173") behavior for parity.
- Line 50: Enable OAuth CSRF protection by adding state: true to the strategy
constructor options in backend/config/passportConfig.js: update the
GoogleStrategy options passed into passport.use(new GoogleStrategy(...)) to
include state: true and likewise update the GitHubStrategy options passed into
passport.use(new GitHubStrategy(...)) to include state: true so the strategy
(not passport.authenticate calls) will validate the state parameter
automatically using the existing express-session.

In `@backend/server.js`:
- Around line 23-29: The session cookie currently only sets maxAge; update the
session middleware configuration (the MongoStore.create + cookie object) to add
cookie.httpOnly: true, cookie.sameSite: 'lax' (or 'strict' if applicable) and
cookie.secure set to true only when running in production (e.g.,
process.env.NODE_ENV === 'production'), and ensure your Express app enables
trust proxy (app.set('trust proxy', 1)) before mounting the session middleware
so secure cookies work behind a proxy/load balancer; keep the store:
MongoStore.create(...) unchanged and apply these cookie and trust-proxy changes
around the session setup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 43d3531b-8bae-4b29-8cbf-4c22173aef4e

📥 Commits

Reviewing files that changed from the base of the PR and between 8d17610 and d482433.

📒 Files selected for processing (8)
  • backend/.env.sample
  • backend/config/passportConfig.js
  • backend/models/User.js
  • backend/package.json
  • backend/routes/auth.js
  • backend/server.js
  • src/pages/Login/Login.tsx
  • src/pages/Signup/Signup.tsx

Comment on lines +44 to +50
let user = await User.findOne({ email: profile.emails[0].value });

if (!user) {
// Create a user placeholder if they are logging in for the first time
user = new User({
username: profile.displayName.replace(/\s+/g, "_").toLowerCase() + "_" + profile.id.substring(0, 5),
email: profile.emails[0].value,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# First, let's find and read the relevant file
fd -t f "passportConfig.js" backend/config/

Repository: GitMetricsLab/github_tracker

Length of output: 105


🏁 Script executed:

# Get the line count first
wc -l backend/config/passportConfig.js

Repository: GitMetricsLab/github_tracker

Length of output: 109


🏁 Script executed:

# Read the relevant section (lines 34-103 for context)
sed -n '34,103p' backend/config/passportConfig.js

Repository: GitMetricsLab/github_tracker

Length of output: 2593


🏁 Script executed:

# Also check the User model to see if username has uniqueness constraint
fd -t f "User" -e "js" -e "ts" | xargs grep -l "username.*unique\|unique.*username" 2>/dev/null | head -5

Repository: GitMetricsLab/github_tracker

Length of output: 54


🏁 Script executed:

# Search for email validation patterns in the file
rg "email" backend/config/passportConfig.js -B 2 -A 2

Repository: GitMetricsLab/github_tracker

Length of output: 2157


🏁 Script executed:

# Find User model file
fd -t f "User" -e "js" -e "ts" models/ || fd -t f "User.js" || fd -t f "user.js" || fd -t f "*chema*" models/

Repository: GitMetricsLab/github_tracker

Length of output: 208


🏁 Script executed:

# Search for User model definition more broadly
rg "new Schema\|export.*User\|module.exports.*User" -A 10 --max-count=3

Repository: GitMetricsLab/github_tracker

Length of output: 54


🏁 Script executed:

# Look for username field definition in the codebase
rg "username.*:.*{" --type js --type ts -B 2 -A 2 | grep -A 5 -B 5 unique

Repository: GitMetricsLab/github_tracker

Length of output: 164


🏁 Script executed:

# Read the User model file
cat -n backend/models/User.js

Repository: GitMetricsLab/github_tracker

Length of output: 1309


🏁 Script executed:

# Search for unique constraints on username
rg "unique\|index" backend/models/User.js -B 2 -A 2

Repository: GitMetricsLab/github_tracker

Length of output: 54


Guard email extraction in Google OAuth strategy before dereferencing array.

Line 44 and line 50 directly access profile.emails[0].value without checking if the array exists. If Google returns a profile without the emails field, this throws a TypeError and crashes the callback. The GitHub strategy already handles this correctly with optional chaining. Add null-safe extraction and reject auth when email is missing.

Proposed fix
- let user = await User.findOne({ email: profile.emails[0].value });
+ const googleEmail = profile.emails?.[0]?.value;
+ if (!googleEmail) {
+   return done(null, false, { message: "Google account email is required" });
+ }
+ let user = await User.findOne({ email: googleEmail });

 ...
-   email: profile.emails[0].value,
+   email: googleEmail,
📝 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
let user = await User.findOne({ email: profile.emails[0].value });
if (!user) {
// Create a user placeholder if they are logging in for the first time
user = new User({
username: profile.displayName.replace(/\s+/g, "_").toLowerCase() + "_" + profile.id.substring(0, 5),
email: profile.emails[0].value,
const googleEmail = profile.emails?.[0]?.value;
if (!googleEmail) {
return done(null, false, { message: "Google account email is required" });
}
let user = await User.findOne({ email: googleEmail });
if (!user) {
// Create a user placeholder if they are logging in for the first time
user = new User({
username: profile.displayName.replace(/\s+/g, "_").toLowerCase() + "_" + profile.id.substring(0, 5),
email: googleEmail,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/config/passportConfig.js` around lines 44 - 50, The Google OAuth
callback dereferences profile.emails[0].value in the User.findOne lookup and
when constructing a new User (username and email) which will throw if
profile.emails is missing; update the Google strategy callback to safely extract
the email (e.g., const email = profile.emails?.[0]?.value) and if email is falsy
call done(null, false, { message: "No email provided by Google" }) to reject
authentication, then use that guarded email value in User.findOne and in the new
User({ ... }) and keep the username generation using profile.displayName but
only if present.

Comment on lines +49 to +50
username: profile.displayName.replace(/\s+/g, "_").toLowerCase() + "_" + profile.id.substring(0, 5),
email: profile.emails[0].value,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle OAuth username collisions against unique index.

Line 49 and Line 85 can generate usernames that collide with existing local/OAuth users, causing duplicate-key failures on first OAuth login. Derive a deterministic unique base (provider + provider id suffix) or retry on duplicate key.

Also applies to: 85-87

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/config/passportConfig.js` around lines 49 - 50, The current OAuth
username construction using profile.displayName + "_" +
profile.id.substring(0,5) (see the username assignment in passportConfig.js) can
collide with existing users; change it to produce a deterministic unique base
that includes the provider to avoid collisions (e.g. use provider + "_" +
profile.id.substring(0,5) or provider + "_" + profile.provider + "_" + id
suffix) or implement a retry-on-duplicate-key routine around the user creation
logic (catch duplicate key errors when creating the User and append/increment a
suffix until creation succeeds); update both places where username is built (the
existing username assignment and the similar block later around lines creating
new OAuth users) and ensure any error handling for duplicate-key (E11000) is
covered so first OAuth login does not fail.

Comment thread backend/models/User.js
Comment on lines +22 to +24
if (this.password === "OAUTH_USER_EXTERNAL_PROVIDER") {
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Replace magic-password provider detection with explicit auth provider fields.

Line 22 and Line 34 key OAuth behavior off a literal password value. That makes auth mode user-input-dependent (a local user choosing this exact password gets a broken login path and plaintext persistence). Use explicit fields like authProvider and providerId instead of inspecting password content.

Suggested direction
 const UserSchema = new mongoose.Schema({
   username: { type: String, required: true, unique: true },
   email: { type: String, required: true, unique: true },
   password: { type: String, required: true },
+  authProvider: { type: String, enum: ["local", "google", "github"], default: "local" },
+  providerId: { type: String, default: null },
 });
 
 UserSchema.pre('save', async function () {
-  if (this.password === "OAUTH_USER_EXTERNAL_PROVIDER") return;
+  if (this.authProvider !== "local") return;
   if (!this.isModified('password')) return;
   const salt = await bcrypt.genSalt(10);
   this.password = await bcrypt.hash(this.password, salt);
 });
 
 UserSchema.methods.comparePassword = async function (enteredPassword) {
-  if (this.password === "OAUTH_USER_EXTERNAL_PROVIDER") return false;
+  if (this.authProvider !== "local") return false;
   return await bcrypt.compare(enteredPassword, this.password);
 };

Also applies to: 34-36

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/models/User.js` around lines 22 - 24, Replace the magic-string check
this.password === "OAUTH_USER_EXTERNAL_PROVIDER" with explicit authProvider and
providerId fields on the User model: add authProvider (e.g. 'local' | 'oauth')
and providerId, update the model defaults/migration to include them, and change
all logic that currently branches on that password literal (e.g., the block in
User.js that returns early and the similar check at lines 34-36) to instead
check authProvider !== 'local' (or authProvider === 'oauth') to skip password
logic; also ensure user-creation and update paths set authProvider/providerId
for external users and that any password setters/validators (e.g.,
validatePassword, setPassword) only operate when authProvider === 'local'.

Comment thread backend/routes/auth.js
Comment thread backend/routes/auth.js Outdated
Comment thread backend/server.js
Comment on lines +23 to +29
store: MongoStore.create({
mongoUrl: process.env.MONGO_URI,
collectionName: 'sessions'
}),
cookie: {
maxAge: 1000 * 60 * 60 * 24
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and examine backend/server.js
find . -name "server.js" -type f | head -20

Repository: GitMetricsLab/github_tracker

Length of output: 92


🏁 Script executed:

# Let's check the directory structure to understand the project layout
git ls-files | grep -E "(backend|server)" | head -30

Repository: GitMetricsLab/github_tracker

Length of output: 355


🏁 Script executed:

# Search for session configuration in the codebase
rg "session\(" -A 10 --max-count 5

Repository: GitMetricsLab/github_tracker

Length of output: 1827


🏁 Script executed:

# Search for trust proxy configuration
rg "trust proxy" -i

Repository: GitMetricsLab/github_tracker

Length of output: 54


🏁 Script executed:

# Search for cookie configuration patterns
rg "cookie\s*:" -B 2 -A 5

Repository: GitMetricsLab/github_tracker

Length of output: 381


🏁 Script executed:

# Read the full backend/server.js file
cat -n backend/server.js

Repository: GitMetricsLab/github_tracker

Length of output: 1614


🏁 Script executed:

# Check Dockerfile configurations to understand deployment context
cat -n backend/Dockerfile.prod

Repository: GitMetricsLab/github_tracker

Length of output: 659


🏁 Script executed:

# Check environment setup in package.json
cat -n backend/package.json | head -30

Repository: GitMetricsLab/github_tracker

Length of output: 996


🏁 Script executed:

# Search for any proxy-related configuration in the codebase
rg "proxy" -i -B 2 -A 2

Repository: GitMetricsLab/github_tracker

Length of output: 54


🏁 Script executed:

# Check if NODE_ENV is used anywhere in the backend
rg "NODE_ENV" backend/

Repository: GitMetricsLab/github_tracker

Length of output: 54


Harden session cookie settings for production.

The session cookie configuration at lines 27-29 sets only maxAge, leaving critical security settings implicit. Add httpOnly, sameSite, secure, and trust proxy settings to prevent XSS session theft, CSRF attacks, and unencrypted transmission.

Proposed fix
+app.set("trust proxy", 1);

 app.use(session({
   secret: process.env.SESSION_SECRET,
   resave: false,
   saveUninitialized: false,
   store: MongoStore.create({
     mongoUrl: process.env.MONGO_URI,
     collectionName: 'sessions'
   }),
   cookie: {
-    maxAge: 1000 * 60 * 60 * 24
+    maxAge: 1000 * 60 * 60 * 24,
+    httpOnly: true,
+    sameSite: "lax",
+    secure: process.env.NODE_ENV === "production",
   }
 }));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/server.js` around lines 23 - 29, The session cookie currently only
sets maxAge; update the session middleware configuration (the MongoStore.create
+ cookie object) to add cookie.httpOnly: true, cookie.sameSite: 'lax' (or
'strict' if applicable) and cookie.secure set to true only when running in
production (e.g., process.env.NODE_ENV === 'production'), and ensure your
Express app enables trust proxy (app.set('trust proxy', 1)) before mounting the
session middleware so secure cookies work behind a proxy/load balancer; keep the
store: MongoStore.create(...) unchanged and apply these cookie and trust-proxy
changes around the session setup.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant