Skip to content

Feat/GitHub signin oauth#336

Open
pratyushranjn wants to merge 5 commits into
GitMetricsLab:mainfrom
pratyushranjn:feat/github-signin-oauth
Open

Feat/GitHub signin oauth#336
pratyushranjn wants to merge 5 commits into
GitMetricsLab:mainfrom
pratyushranjn:feat/github-signin-oauth

Conversation

@pratyushranjn
Copy link
Copy Markdown

@pratyushranjn pratyushranjn commented May 19, 2026

Related Issue

Closes #29


Description

Implemented GitHub OAuth Sign-In using Passport GitHub Strategy.

Features added:

  • GitHub OAuth authentication
  • User creation/login through GitHub
  • Account linking with existing email
  • Session-based authentication
  • GitHub avatar storage
  • Graceful handling for failed/unconfigured OAuth states

Also updated .env.sample with GitHub OAuth environment variables.


How Has This Been Tested?

  • Tested local signup/login flow
  • Tested GitHub OAuth login flow
  • Verified MongoDB user creation
  • Verified existing account linking
  • Tested repeated GitHub login sessions

Screenshots

Screenshot 2026-05-17 144729 Screenshot 2026-05-17 144706 Screenshot 2026-05-17 144103 Screenshot 2026-05-19 220815

Type of Change

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

Summary by CodeRabbit

  • New Features

    • GitHub OAuth added as a sign-in option on the login page with user-facing success/failure flows.
    • Login UI now shows a dedicated "Sign in with GitHub" action and loading state.
  • Bug Fixes

    • Clearer authentication error messages and redirect behavior for OAuth outcomes.
  • Documentation

    • Added an environment configuration template for local setup.
  • Chores

    • Added runtime dependency to support GitHub OAuth.

Review Change Stack

@netlify
Copy link
Copy Markdown

netlify Bot commented May 19, 2026

Deploy Preview for github-spy ready!

Name Link
🔨 Latest commit cb0157b
🔍 Latest deploy log https://app.netlify.com/projects/github-spy/deploys/6a107e39de638d00078d7828
😎 Deploy Preview https://deploy-preview-336--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.

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.

@pratyushranjn
Copy link
Copy Markdown
Author

Note: The Netlify deploy preview currently shows /undefined/api/auth/github because the preview environment does not have VITE_BACKEND_URL configured.

The GitHub OAuth flow has been tested locally and works correctly with the required environment variables configured.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

Warning

Rate limit exceeded

@pratyushranjn has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 57 minutes and 9 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: 63f22a1e-d20a-428e-a639-fbbb74c67521

📥 Commits

Reviewing files that changed from the base of the PR and between 8a1d518 and cb0157b.

📒 Files selected for processing (1)
  • src/pages/Login/Login.tsx
📝 Walkthrough

Walkthrough

This PR implements GitHub OAuth sign-in alongside existing email/password authentication. The backend adds environment configuration, updates the User model to support GitHub-only accounts, implements Passport GitHub strategy and local strategy guards, and provides auth routes with configuration checks. The frontend adds a GitHub sign-in button, handles OAuth callback status via query parameters, and displays status toasts.

Changes

GitHub OAuth Sign-In Implementation

Layer / File(s) Summary
Environment and server configuration
backend/.env.sample, backend/server.js, backend/package.json, backend/routes/auth.js
Environment template defines server port, MongoDB URI, session secret, GitHub OAuth client credentials, callback URL, and frontend base URL. Server configures CORS to allow only the frontend origin with credentials enabled. Session middleware includes a fallback session secret and cookie settings (httpOnly, sameSite: 'lax', secure in production). Auth routes derive frontendUrl and isGitHubConfigured. passport-github2 added to dependencies.
User model schema for GitHub auth
backend/models/User.js
Email and password fields now required only when githubId is absent. New githubId field with unique and sparse. New avatar field added. pre("save") hook formatting updated and comparePassword returns false early when no stored password.
Passport authentication strategies
backend/config/passportConfig.js
LocalStrategy now returns a unified "Invalid email or password" for missing users or mismatched passwords and rejects accounts without a stored password with "Use GitHub sign in for this account". Conditional GitHub strategy registers when client env vars exist, resolves or creates users by githubId (with email fallback), backfills githubId/email/avatar when needed, persists the user, and returns { id, username, email }.
GitHub OAuth routes and redirects
backend/routes/auth.js
/github checks OAuth config and redirects to ${frontendUrl}/login?githubAuth=not_configured when missing; otherwise initiates Passport OAuth with user:email scope. /github/callback also short-circuits when unconfigured, redirects failures to ?githubAuth=failed and successes to ?githubAuth=success.
Frontend GitHub login UI and callback handling
src/pages/Login/Login.tsx
Adds githubLoading state, handleGitHubSignIn to redirect to backend auth endpoint, and a useEffect to read githubAuth query param to show toasts for success/failed/not_configured, clear the query string, and navigate to /track after success. Adds "Sign In with GitHub" button with animated connecting indicator and minor styling adjustments.

Sequence Diagram

sequenceDiagram
  participant User
  participant LoginUI as Login UI
  participant Backend as Backend Auth
  participant GitHub as GitHub OAuth
  participant Database as User DB

  User->>LoginUI: Click "Sign In with GitHub"
  LoginUI->>Backend: GET /api/auth/github
  alt GitHub OAuth Configured
    Backend->>GitHub: Initiate OAuth (scope: user:email)
    GitHub->>User: Redirect to GitHub login
    User->>GitHub: Authenticate & authorize
    GitHub->>Backend: Redirect to /github/callback with code
    Backend->>GitHub: Exchange code for profile
    GitHub->>Backend: Return email, avatar, githubId
    Backend->>Database: Lookup/create user by githubId
    Database->>Backend: Return/save user
    Backend->>LoginUI: Redirect with githubAuth=success
    LoginUI->>User: Show success toast, navigate to /track
  else GitHub OAuth Not Configured
    Backend->>LoginUI: Redirect with githubAuth=not_configured
    LoginUI->>User: Show configuration error toast
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

type:feature

Suggested reviewers

  • mehul-m-prajapati

Poem

🐰 I hopped across the login pane,
Brought GitHub magic, neat and plain.
OAuth hops from site to site,
Back to /track when all's alright.
A carrot-toast for successful sign-in!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Feat/GitHub signin oauth' uses unconventional format with slashes but accurately summarizes the main change: implementing GitHub OAuth sign-in functionality.
Description check ✅ Passed The PR description follows the template structure, includes all required sections (Related Issue, Description, Testing, Screenshots, Type of Change), and provides comprehensive details about the implementation.
Linked Issues check ✅ Passed The PR implementation fully addresses issue #29 by adding GitHub OAuth sign-in with user creation, account linking, session authentication, avatar storage, and graceful error handling.
Out of Scope Changes check ✅ Passed All changes are scope-appropriate: environment configuration, GitHub OAuth strategy implementation, user schema updates for GitHub authentication, backend routes handling, frontend login UI integration, and dependencies—all directly supporting the GitHub OAuth feature.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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
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: 5

Caution

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

⚠️ Outside diff range comments (1)
backend/config/passportConfig.js (1)

12-23: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid account enumeration in login failures.

Returning different messages for unknown email vs wrong password leaks account existence.

Suggested fix
                 const user = await User.findOne({ email });
-                if (!user) {
-                    return done(null, false, { message: 'Email is invalid ' });
-                }
-
-                if (!user.password) {
-                    return done(null, false, { message: 'Use GitHub sign in for this account' });
-                }
+                if (!user || !user.password) {
+                    return done(null, false, { message: 'Invalid credentials' });
+                }
@@
                 const isMatch = await user.comparePassword(password);
                 if (!isMatch) {
-                    return done(null, false, { message: 'Invalid password' });
+                    return done(null, false, { message: 'Invalid credentials' });
                 }
🤖 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 12 - 23, The current passport
local verify callback leaks account existence by returning different messages
for missing user, accounts without a password, and wrong passwords; change the
logic in the verify function (the callback that calls done) so that any
authentication failure (no user found, user.password falsy, or comparePassword
returns false) returns the same generic error via done(null, false, { message:
'Invalid email or password' }) instead of distinct strings; keep the call to
user.comparePassword but ensure its result is masked by the unified error
message to avoid account enumeration.
🤖 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 48-64: The code is persisting email: null which breaks the sparse
unique index; update the User creation and update logic in passportConfig.js so
you only set the email field when primaryEmail is truthy (e.g., omit the email
key or set it to undefined when primaryEmail is falsy) — change the new
User({... email: primaryEmail ...}) to conditionally include email, and
similarly change any later assignment like user.email = primaryEmail to only run
when primaryEmail is present; reference the User model, the code in
passportConfig.js that constructs new User and the code path that updates
user.email.
- Around line 39-45: The GitHubStrategy instantiation in passportConfig.js
currently omits OAuth2 CSRF state handling; update the new GitHubStrategy(...)
options to enable state by adding the OAuth2 state option (e.g., state: true or
provide a state store) so Passport generates and validates the state parameter
during the GitHub login/callback flow, and ensure your session middleware is
used so the state store can persist between request and callback (refer to the
GitHubStrategy constructor and the surrounding passport/session setup).

In `@backend/server.js`:
- Line 26: The session middleware is using a fallback 'dev-session-secret' which
should not be used in production; before calling the express-session setup (the
session({...}) call and its secret property), check NODE_ENV === 'production'
and if process.env.SESSION_SECRET is missing, throw an error or call
process.exit(1) to fail fast; update the session configuration to use
process.env.SESSION_SECRET (no default) for the secret field and ensure any
environment-dependent sameSite/secure options are set appropriately for
production deployments.

In `@src/pages/Login/Login.tsx`:
- Around line 59-83: The useEffect in Login.tsx schedules a setTimeout for
navigate("/track") but never clears it on unmount; capture the timer id (e.g.,
const timer = window.setTimeout(...) or let redirectTimer) when githubAuthStatus
=== "success" and return a cleanup function from the React.useEffect that calls
clearTimeout(redirectTimer) to cancel the pending navigation if the component
unmounts or deps change; keep the existing window.history.replaceState and toast
calls intact.
- Around line 54-57: handleGitHubSignIn currently unconditionally redirects
using backendUrl which can be undefined; update the handler to check that
backendUrl (the VITE_BACKEND_URL-derived value) is truthy before setting loading
and assigning window.location.href. If backendUrl is falsy, do not
setGithubLoading(true) and instead route the user to a safe fallback (e.g.,
navigate to a local "/not_configured" page or show an error/toast) so the app
doesn't send the browser to "undefined/api/auth/github". Modify the
handleGitHubSignIn function and related use of setGithubLoading to implement
this guard.

---

Outside diff comments:
In `@backend/config/passportConfig.js`:
- Around line 12-23: The current passport local verify callback leaks account
existence by returning different messages for missing user, accounts without a
password, and wrong passwords; change the logic in the verify function (the
callback that calls done) so that any authentication failure (no user found,
user.password falsy, or comparePassword returns false) returns the same generic
error via done(null, false, { message: 'Invalid email or password' }) instead of
distinct strings; keep the call to user.comparePassword but ensure its result is
masked by the unified error message to avoid account enumeration.
🪄 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: a1723c7c-5212-44e4-8b19-07ab9ba8ae99

📥 Commits

Reviewing files that changed from the base of the PR and between 9d34c19 and 493a341.

📒 Files selected for processing (7)
  • 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

Comment thread backend/config/passportConfig.js Outdated
Comment thread backend/config/passportConfig.js Outdated
Comment thread backend/server.js
app.use(bodyParser.json());
app.use(session({
secret: process.env.SESSION_SECRET,
secret: process.env.SESSION_SECRET || 'dev-session-secret',
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:

#!/bin/bash
set -euo pipefail

# Locate and inspect backend/server.js around the referenced lines
if [ -f backend/server.js ]; then
  echo "=== backend/server.js (head) ==="
  sed -n '1,120p' backend/server.js | cat -n
  echo
  echo "=== Search for session config and SESSION_SECRET ==="
  rg -n "SESSION_SECRET|express-session|app\\.use\\(session\\(|cookie:|sameSite|trust proxy|cors\\(" backend/server.js || true
else
  echo "backend/server.js not found"
  ls -la backend || true
fi

# Check for any other session middleware usage elsewhere
echo
echo "=== Search repo for express-session usage ==="
rg -n "express-session|app\\.use\\(session\\(" . || true

# Check for CORS usage and FRONTEND_URL usage
echo
echo "=== Search repo for CORS/front-end URL config ==="
rg -n "cors\\(|FRONTEND_URL|frontendUrl" . || true

Repository: GitMetricsLab/github_tracker

Length of output: 3224


Fail fast if SESSION_SECRET is missing in production (backend/server.js:26)

express-session currently falls back to 'dev-session-secret', making session cookie signing predictable when prod env config is incomplete. (Also, with sameSite: 'lax', cross-site credentialed requests may fail if the frontend is on a different site.)

Suggested fix
 const app = express();
 const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:5173';
+const isProd = process.env.NODE_ENV === 'production';
+if (isProd && !process.env.SESSION_SECRET) {
+    throw new Error('SESSION_SECRET must be set in production');
+}
@@
 app.use(session({
-    secret: process.env.SESSION_SECRET || 'dev-session-secret',
+    secret: process.env.SESSION_SECRET || 'dev-session-secret',
🤖 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` at line 26, The session middleware is using a fallback
'dev-session-secret' which should not be used in production; before calling the
express-session setup (the session({...}) call and its secret property), check
NODE_ENV === 'production' and if process.env.SESSION_SECRET is missing, throw an
error or call process.exit(1) to fail fast; update the session configuration to
use process.env.SESSION_SECRET (no default) for the secret field and ensure any
environment-dependent sameSite/secure options are set appropriately for
production deployments.

Comment thread src/pages/Login/Login.tsx
Comment on lines +54 to +57
const handleGitHubSignIn = () => {
setGithubLoading(true);
window.location.href = `${backendUrl}/api/auth/github`;
};
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

Guard the GitHub redirect when VITE_BACKEND_URL is missing.

If backendUrl is unset, this sends the browser to undefined/api/auth/github, so the backend's not_configured fallback is never reached and the new sign-in path hard-fails.

Suggested fix
  const handleGitHubSignIn = () => {
+   if (!backendUrl) {
+     toast.error("GitHub sign in is not configured for this deployment.");
+     return;
+   }
+
    setGithubLoading(true);
-   window.location.href = `${backendUrl}/api/auth/github`;
+   window.location.href = `${backendUrl}/api/auth/github`;
  };
🤖 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 `@src/pages/Login/Login.tsx` around lines 54 - 57, handleGitHubSignIn currently
unconditionally redirects using backendUrl which can be undefined; update the
handler to check that backendUrl (the VITE_BACKEND_URL-derived value) is truthy
before setting loading and assigning window.location.href. If backendUrl is
falsy, do not setGithubLoading(true) and instead route the user to a safe
fallback (e.g., navigate to a local "/not_configured" page or show an
error/toast) so the app doesn't send the browser to "undefined/api/auth/github".
Modify the handleGitHubSignIn function and related use of setGithubLoading to
implement this guard.

Comment thread src/pages/Login/Login.tsx
Comment on lines +59 to +83
React.useEffect(() => {
const githubAuthStatus = new URLSearchParams(window.location.search).get("githubAuth");

if (githubAuthStatus === "success") {
toast.success("GitHub login successful");

window.history.replaceState({}, document.title, window.location.pathname);

setTimeout(() => {
navigate("/track");
}, 1000);
}

if (githubAuthStatus === "failed") {
toast.error("GitHub sign in failed. Please try again.");

window.history.replaceState({}, document.title, window.location.pathname);
}

if (githubAuthStatus === "not_configured") {
toast.error("GitHub sign in is not configured on server.");

window.history.replaceState({}, document.title, window.location.pathname);
}
}, [navigate]);
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 | 🟡 Minor | ⚡ Quick win

Clear the delayed redirect timer on cleanup.

Line 67 schedules a navigation that still fires if the user leaves /login within that 1-second window, which can unexpectedly pull them back to /track.

Suggested fix
  React.useEffect(() => {
+   let redirectTimer: number | undefined;
    const githubAuthStatus = new URLSearchParams(window.location.search).get("githubAuth");

    if (githubAuthStatus === "success") {
      toast.success("GitHub login successful");

      window.history.replaceState({}, document.title, window.location.pathname);

-     setTimeout(() => {
+     redirectTimer = window.setTimeout(() => {
        navigate("/track");
      }, 1000);
    }

    if (githubAuthStatus === "failed") {
      toast.error("GitHub sign in failed. Please try again.");

      window.history.replaceState({}, document.title, window.location.pathname);
    }

    if (githubAuthStatus === "not_configured") {
      toast.error("GitHub sign in is not configured on server.");

      window.history.replaceState({}, document.title, window.location.pathname);
    }
+
+   return () => {
+     if (redirectTimer) {
+       window.clearTimeout(redirectTimer);
+     }
+   };
  }, [navigate]);
📝 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
React.useEffect(() => {
const githubAuthStatus = new URLSearchParams(window.location.search).get("githubAuth");
if (githubAuthStatus === "success") {
toast.success("GitHub login successful");
window.history.replaceState({}, document.title, window.location.pathname);
setTimeout(() => {
navigate("/track");
}, 1000);
}
if (githubAuthStatus === "failed") {
toast.error("GitHub sign in failed. Please try again.");
window.history.replaceState({}, document.title, window.location.pathname);
}
if (githubAuthStatus === "not_configured") {
toast.error("GitHub sign in is not configured on server.");
window.history.replaceState({}, document.title, window.location.pathname);
}
}, [navigate]);
React.useEffect(() => {
let redirectTimer: number | undefined;
const githubAuthStatus = new URLSearchParams(window.location.search).get("githubAuth");
if (githubAuthStatus === "success") {
toast.success("GitHub login successful");
window.history.replaceState({}, document.title, window.location.pathname);
redirectTimer = window.setTimeout(() => {
navigate("/track");
}, 1000);
}
if (githubAuthStatus === "failed") {
toast.error("GitHub sign in failed. Please try again.");
window.history.replaceState({}, document.title, window.location.pathname);
}
if (githubAuthStatus === "not_configured") {
toast.error("GitHub sign in is not configured on server.");
window.history.replaceState({}, document.title, window.location.pathname);
}
return () => {
if (redirectTimer) {
window.clearTimeout(redirectTimer);
}
};
}, [navigate]);
🤖 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 `@src/pages/Login/Login.tsx` around lines 59 - 83, The useEffect in Login.tsx
schedules a setTimeout for navigate("/track") but never clears it on unmount;
capture the timer id (e.g., const timer = window.setTimeout(...) or let
redirectTimer) when githubAuthStatus === "success" and return a cleanup function
from the React.useEffect that calls clearTimeout(redirectTimer) to cancel the
pending navigation if the component unmounts or deps change; keep the existing
window.history.replaceState and toast calls intact.

@mehul-m-prajapati
Copy link
Copy Markdown
Collaborator

@pratyushranjn : oauth will be done after we have proper backend, can't merge this PR for now

@pratyushranjn
Copy link
Copy Markdown
Author

Understood @mehul-m-prajapati, thanks for the clarification.
I’ll keep the PR as-is for now and can revisit/update the OAuth integration once the backend structure is finalized.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🚀 Feature: Add "Sign In with GitHub"

2 participants