Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions backend/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,26 @@ const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");

const UserSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
username: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
},
Comment on lines +14 to +16
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

Potential runtime errors in password hashing for Google users.

Making the password field optional could cause issues in the pre-save hook (lines 25-37) and comparePassword method (lines 40-41), as these assume a password exists and attempt to hash/compare undefined values.

Apply this diff to handle optional passwords properly:

 UserSchema.pre('save', async function (next) {
-    if (!this.isModified('password'))
+    if (!this.isModified('password') || !this.password)
         return next();

     try {
       const salt = await bcrypt.genSalt(10);
       this.password = await bcrypt.hash(this.password, salt);
       next();
     } catch (err) {
       return next(err);
     }
 });

 // Compare passwords during login
 UserSchema.methods.comparePassword = async function (enteredPassword) {
+    if (!this.password) {
+        return false;
+    }
     return await bcrypt.compare(enteredPassword, this.password);
 };
📝 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
password: {
type: String,
},
UserSchema.pre('save', async function (next) {
// Only hash if the password was modified and actually exists
if (!this.isModified('password') || !this.password) {
return next();
}
try {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
} catch (err) {
return next(err);
}
});
// Compare passwords during login
UserSchema.methods.comparePassword = async function (enteredPassword) {
// If there's no stored password (e.g. OAuth user), always fail comparison
if (!this.password) {
return false;
}
return await bcrypt.compare(enteredPassword, this.password);
};
🤖 Prompt for AI Agents
In backend/models/User.js around lines 14 to 16 (and affecting the pre-save hook
at ~25-37 and comparePassword at ~40-41), the schema makes password optional but
the pre-save hook and comparePassword assume a defined password; update them so
the pre-save hook only runs hashing when this.isModified('password') &&
this.password is truthy, and skip hashing for users without a password (e.g.,
Google OAuth). Also change comparePassword to immediately return false (or a
rejected Promise) if the stored password is falsy, and only call bcrypt.compare
when both the candidate password and stored hash exist; keep the same
async/callback pattern used elsewhere. This prevents attempts to hash/compare
undefined values for OAuth users.

googleId: {
type: String,
unique: true,
sparse: true,
},
});


UserSchema.pre('save', async function (next) {

if (!this.isModified('password'))
Expand Down
26 changes: 26 additions & 0 deletions backend/routes/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,31 @@ router.post("/login", passport.authenticate('local'), (req, res) => {
res.status(200).json( { message: 'Login successful', user: req.user } );
});

router.post("/google-login", async (req, res) => {
try {
const { email, name, googleId } = req.body;

let user = await User.findOne({ email });

if (!user) {
user = new User({
username: name,
email,
googleId,
});
await user.save();
console.log("New Google user saved:", user);
} else {
console.log("Google user found:", user);
}

return res.json({ message: "Login successful", user });
} catch (error) {
console.error("Google login error:", error);
return res.status(500).json({ message: "Server error" });
}
});
Comment on lines +30 to +53
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 security and account management issues need to be addressed.

The Google login endpoint has several critical issues:

  1. Account security vulnerability: When finding an existing user by email, the endpoint doesn't verify the googleId matches, potentially allowing account takeover if someone knows another user's email.

  2. Missing account linking: If a user exists with email/password but no googleId, the endpoint doesn't update their record to link the Google account.

  3. No authentication state management: Unlike the Passport-based /login route, this endpoint doesn't establish a session or provide authentication context.

Apply this diff to fix the security and account management issues:

 router.post("/google-login", async (req, res) => {
   try {
     const { email, name, googleId } = req.body;

     let user = await User.findOne({ email });

     if (!user) {
       user = new User({
         username: name,
         email,
         googleId,
       });
       await user.save();
       console.log("New Google user saved:", user); 
     } else {
-      console.log("Google user found:", user);
+      // Link Google account if not already linked
+      if (!user.googleId) {
+        user.googleId = googleId;
+        await user.save();
+        console.log("Google account linked to existing user:", user);
+      } else if (user.googleId !== googleId) {
+        return res.status(400).json({ message: "Email is associated with a different Google account" });
+      } else {
+        console.log("Google user found:", user);
+      }
     }

+    // Establish session like the regular login route
+    req.login(user, (err) => {
+      if (err) {
+        return res.status(500).json({ message: "Session creation failed" });
+      }
       return res.json({ message: "Login successful", user });
+    });
   } catch (error) {
     console.error("Google login error:", error);
     return res.status(500).json({ message: "Server error" });
   }
 });
📝 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
router.post("/google-login", async (req, res) => {
try {
const { email, name, googleId } = req.body;
let user = await User.findOne({ email });
if (!user) {
user = new User({
username: name,
email,
googleId,
});
await user.save();
console.log("New Google user saved:", user);
} else {
console.log("Google user found:", user);
}
return res.json({ message: "Login successful", user });
} catch (error) {
console.error("Google login error:", error);
return res.status(500).json({ message: "Server error" });
}
});
router.post("/google-login", async (req, res) => {
try {
const { email, name, googleId } = req.body;
let user = await User.findOne({ email });
if (!user) {
user = new User({
username: name,
email,
googleId,
});
await user.save();
console.log("New Google user saved:", user);
} else {
// Link Google account if not already linked
if (!user.googleId) {
user.googleId = googleId;
await user.save();
console.log("Google account linked to existing user:", user);
} else if (user.googleId !== googleId) {
return res
.status(400)
.json({ message: "Email is associated with a different Google account" });
} else {
console.log("Google user found:", user);
}
}
// Establish session like the regular login route
req.login(user, (err) => {
if (err) {
return res
.status(500)
.json({ message: "Session creation failed" });
}
return res.json({ message: "Login successful", user });
});
} catch (error) {
console.error("Google login error:", error);
return res
.status(500)
.json({ message: "Server error" });
}
});
🤖 Prompt for AI Agents
In backend/routes/auth.js around lines 30 to 53, the Google login handler
currently trusts email alone and neither verifies the googleId nor links
accounts or establishes auth state; update it to first validate incoming payload
(email and googleId), then lookup User by email and: if found and user.googleId
exists, ensure it matches the provided googleId and reject with 401 on mismatch;
if found and user.googleId is absent, set user.googleId to the provided value
and save to link the Google account; if not found, create a new user with
googleId and save; after any successful lookup or creation, establish an
authenticated session identical to your /login flow (e.g., call req.login or
issue the same JWT/session cookie you use elsewhere) and return the user without
sensitive fields, while returning appropriate 4xx errors for invalid payloads
and mismatched googleId to prevent account takeover.


// Logout route
router.get("/logout", (req, res) => {

Expand All @@ -40,3 +65,4 @@ router.get("/logout", (req, res) => {
});

module.exports = router;

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
"@primer/octicons-react": "^19.15.5",
"@vitejs/plugin-react": "^4.3.3",
"axios": "^1.7.7",
"firebase": "^12.1.0",
"framer-motion": "^12.23.12",
"i": "^0.3.7",
"lucide-react": "^0.525.0",
"octokit": "^4.0.2",
"postcss": "^8.4.47",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-firebase-hooks": "^5.1.1",
"react-hot-toast": "^2.4.1",
Comment on lines +22 to 31
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify and remove potentially unused or unintended dependencies ("i", "react-firebase-hooks")

The newly added "i" and "react-firebase-hooks" packages don’t appear in the provided code paths. If unused, they increase install size and attack surface.

Run this to confirm usage:

If unused, remove them:

   "dependencies": {
     ...
-    "firebase": "^12.1.0",
+    "firebase": "^12.1.0",
     ...
-    "i": "^0.3.7",
+    // "i" removed if unused
     ...
-    "react-firebase-hooks": "^5.1.1",
+    // "react-firebase-hooks" removed if unused
     ...
   },

🏁 Script executed:

#!/bin/bash
# Find direct imports/usages of 'i' and 'react-firebase-hooks'
rg -n -S $'from [\'"]i[\'"]|require\\([\'"]i[\'"]\\)' || true
rg -n -S 'react-firebase-hooks' || true

Length of output: 153


Remove unused dependencies “i” and “react-firebase-hooks”
A project-wide search found no imports or requires for these two packages beyond their entries in package.json. Removing them will slim install size and reduce attack surface.

• package.json – lines for
– “i” (^0.3.7)
– “react-firebase-hooks” (^5.1.1)

Suggested diff:

   "dependencies": {
     "firebase": "^12.1.0",
     "framer-motion": "^12.23.12",
-    "i": "^0.3.7",
-    "react-firebase-hooks": "^5.1.1",
     "lucide-react": "^0.525.0",
     "octokit": "^4.0.2",
     …
   },
📝 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
"firebase": "^12.1.0",
"framer-motion": "^12.23.12",
"i": "^0.3.7",
"lucide-react": "^0.525.0",
"octokit": "^4.0.2",
"postcss": "^8.4.47",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-firebase-hooks": "^5.1.1",
"react-hot-toast": "^2.4.1",
"dependencies": {
"firebase": "^12.1.0",
"framer-motion": "^12.23.12",
"lucide-react": "^0.525.0",
"octokit": "^4.0.2",
"postcss": "^8.4.47",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hot-toast": "^2.4.1",
},
🤖 Prompt for AI Agents
In package.json around lines 22 to 31, remove the unused dependency entries for
"i" (^0.3.7) and "react-firebase-hooks" (^5.1.1) from the dependencies section,
then run your package manager to update the lockfile (npm install or yarn
install) and commit the updated package.json and lockfile; after that run the
test/build steps to ensure nothing breaks and update any CI cache if necessary.

"react-icons": "^5.3.0",
"react-router-dom": "^6.28.0",
Expand Down
1 change: 1 addition & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

import Navbar from "./components/Navbar";
import Footer from "./components/Footer";
import ScrollProgressBar from "./components/ScrollProgressBar";
Expand Down
60 changes: 60 additions & 0 deletions src/components/GoogleSignIn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { signInWithGoogle } from "../hooks/firebase";
import React, { useState } from "react";
import axios from "axios";
import { useNavigate } from "react-router-dom";

const backendUrl = import.meta.env.VITE_BACKEND_URL;

const GoogleSignIn = () => {
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();

const handleSignIn = async () => {
setIsLoading(true);
try {
const userCredential = await signInWithGoogle();
console.log("Google userCredential:", userCredential);

if (userCredential && userCredential.user) {
const idToken = await userCredential.user.getIdToken();
const { email, displayName, uid } = userCredential.user;
console.log("Google user info:", { email, displayName, uid });

const response = await axios.post(
`${backendUrl}/api/auth/google-login`,
{
email,
name: displayName,
googleId: uid,
}
);
console.log("Backend response:", response.data);

if (response.data.message === "Login successful") {
navigate("/");
} else {
alert(response.data.message || "Google login failed");
}
} else {
alert("No user returned from Google sign-in");
}
} catch (error) {
console.error("Google sign-in error:", error);
alert("Google sign-in failed");
} finally {
setIsLoading(false);
}
};
Comment on lines +12 to +47
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Several improvements needed for production readiness.

The core authentication flow is functional but has areas for improvement:

  1. Unused ID token: The idToken is retrieved but never used, which may indicate incomplete Firebase authentication verification.

  2. User experience: Using alert() for error messages is not ideal for modern web applications - consider using toast notifications or inline error states.

  3. Error handling: Generic error messages don't help users understand specific issues (network failures, auth cancellation, etc.).

  4. Security consideration: The backend endpoint lacks proper session management (see backend review comments).

Consider these improvements for better UX and security:

   const handleSignIn = async () => {
     setIsLoading(true);
     try {
       const userCredential = await signInWithGoogle();
       console.log("Google userCredential:", userCredential);

       if (userCredential && userCredential.user) {
-        const idToken = await userCredential.user.getIdToken();
         const { email, displayName, uid } = userCredential.user;
         console.log("Google user info:", { email, displayName, uid });

         const response = await axios.post(
           `${backendUrl}/api/auth/google-login`,
           {
             email,
             name: displayName,
             googleId: uid,
           }
         );
         console.log("Backend response:", response.data);

         if (response.data.message === "Login successful") {
           navigate("/");
         } else {
-          alert(response.data.message || "Google login failed");
+          // TODO: Replace with proper error state or toast notification
+          console.error("Login failed:", response.data.message);
         }
       } else {
-        alert("No user returned from Google sign-in");
+        console.error("No user returned from Google sign-in");
       }
     } catch (error) {
       console.error("Google sign-in error:", error);
-      alert("Google sign-in failed");
+      // TODO: Handle specific error types (user cancelled, network error, etc.)
     } finally {
       setIsLoading(false);
     }
   };
📝 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 handleSignIn = async () => {
setIsLoading(true);
try {
const userCredential = await signInWithGoogle();
console.log("Google userCredential:", userCredential);
if (userCredential && userCredential.user) {
const idToken = await userCredential.user.getIdToken();
const { email, displayName, uid } = userCredential.user;
console.log("Google user info:", { email, displayName, uid });
const response = await axios.post(
`${backendUrl}/api/auth/google-login`,
{
email,
name: displayName,
googleId: uid,
}
);
console.log("Backend response:", response.data);
if (response.data.message === "Login successful") {
navigate("/");
} else {
alert(response.data.message || "Google login failed");
}
} else {
alert("No user returned from Google sign-in");
}
} catch (error) {
console.error("Google sign-in error:", error);
alert("Google sign-in failed");
} finally {
setIsLoading(false);
}
};
const handleSignIn = async () => {
setIsLoading(true);
try {
const userCredential = await signInWithGoogle();
console.log("Google userCredential:", userCredential);
if (userCredential && userCredential.user) {
const { email, displayName, uid } = userCredential.user;
console.log("Google user info:", { email, displayName, uid });
const response = await axios.post(
`${backendUrl}/api/auth/google-login`,
{
email,
name: displayName,
googleId: uid,
}
);
console.log("Backend response:", response.data);
if (response.data.message === "Login successful") {
navigate("/");
} else {
// TODO: Replace with proper error state or toast notification
console.error("Login failed:", response.data.message);
}
} else {
console.error("No user returned from Google sign-in");
}
} catch (error) {
console.error("Google sign-in error:", error);
// TODO: Handle specific error types (user cancelled, network error, etc.)
} finally {
setIsLoading(false);
}
};
🤖 Prompt for AI Agents
In src/components/GoogleSignIn.tsx around lines 12 to 47, the handler retrieves
an idToken but never uses it and shows raw alerts on errors; update the flow to
send the idToken to the backend (either in the POST body or Authorization
header) so the server can verify the Firebase token and establish a session,
replace alert() calls with a UI-friendly error mechanism (toast or component
state like setError and render inline messages), and improve error handling by
distinguishing Axios/network errors, Firebase auth cancellations, and unexpected
exceptions to display meaningful messages and log full error details; keep
setIsLoading(false) in finally.


return (
<button
onClick={handleSignIn}
disabled={isLoading}
className="w-full bg-gradient-to-r from-purple-600 via-pink-600 to-indigo-600 text-white py-4 px-6 rounded-2xl font-semibold focus:ring-4 focus:ring-purple-500/50 transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed transform hover:scale-105 shadow-lg"
>
{isLoading ? "Signing in..." : "Sign in with Google"}
</button>
);
};

export default GoogleSignIn;
23 changes: 23 additions & 0 deletions src/hooks/firebase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { initializeApp } from 'firebase/app';
import { getAuth, GoogleAuthProvider, signInWithPopup, setPersistence, browserLocalPersistence } from 'firebase/auth';

const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID
};
Comment on lines +4 to +11
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fail fast if required Firebase env vars are missing

InitializeApp will throw opaque errors later if any of these are undefined. Guard early with a clear message.

Apply this check:

 const firebaseConfig = {
   apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
   authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
   projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
   storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
   messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
   appId: import.meta.env.VITE_FIREBASE_APP_ID
 };
+
+const missing = Object.entries(firebaseConfig)
+  .filter(([, v]) => !v)
+  .map(([k]) => k);
+if (missing.length) {
+  throw new Error(`Missing Firebase env vars: ${missing.join(", ")}`);
+}
📝 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 firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID
};
const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID
};
const missing = Object.entries(firebaseConfig)
.filter(([, v]) => !v)
.map(([k]) => k);
if (missing.length) {
throw new Error(`Missing Firebase env vars: ${missing.join(", ")}`);
}
🤖 Prompt for AI Agents
In src/hooks/firebase.ts around lines 4 to 11, the firebaseConfig is built
directly from import.meta.env so missing env vars cause opaque errors later; add
a guard that enumerates the required variable names, checks import.meta.env for
each, collects any missing keys, and if any are missing throw (or console.error
then throw) a clear Error listing which env vars are absent (and stop
initialization). Run this check before using firebaseConfig or calling
initializeApp so the failure is immediate and the error message is actionable.


const app = initializeApp(firebaseConfig);

const auth = getAuth(app);
const googleAuthProvider = new GoogleAuthProvider();

const signInWithGoogle = async () => {
await setPersistence(auth, browserLocalPersistence);
return await signInWithPopup(auth, googleAuthProvider);
};
Comment on lines +18 to +21
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Critical: Send and verify Firebase ID token on the backend; don’t trust client-supplied UID/email

The current flow returns a UserCredential to the caller, which then posts uid/email to your backend (per PR description). Without verifying an ID token server-side, an attacker can spoof uid/email. The backend must verify a Firebase ID token using the Admin SDK (or Google’s token verification) before creating/authenticating a user.

Refactor to return the ID token alongside the credential so the frontend can send it to the backend:

-const signInWithGoogle = async () => {
-  await setPersistence(auth, browserLocalPersistence);
-  return await signInWithPopup(auth, googleAuthProvider);
-};
+const signInWithGoogle = async () => {
+  await setPersistence(auth, browserLocalPersistence);
+  const cred = await signInWithPopup(auth, googleAuthProvider);
+  const idToken = await cred.user.getIdToken();
+  return { cred, idToken };
+};

Then, POST the idToken to the backend (e.g., in GoogleSignIn.tsx) and verify it there with Firebase Admin before trusting any user claims.

Optionally add a popup fallback for Safari/mobile:

-import { getAuth, GoogleAuthProvider, signInWithPopup, setPersistence, browserLocalPersistence } from 'firebase/auth';
+import { getAuth, GoogleAuthProvider, signInWithPopup, signInWithRedirect, setPersistence, browserLocalPersistence } from 'firebase/auth';
...
-  const cred = await signInWithPopup(auth, googleAuthProvider);
+  let cred;
+  try {
+    cred = await signInWithPopup(auth, googleAuthProvider);
+  } catch (e: any) {
+    if (e?.code === 'auth/popup-blocked' || e?.code === 'auth/cancelled-popup-request') {
+      await signInWithRedirect(auth, googleAuthProvider);
+      return { cred: undefined, idToken: undefined }; // handle redirect result in caller if you add it
+    }
+    throw e;
+  }

Confirm the backend’s /google-login endpoint verifies a Firebase ID token (not raw uid/email) using the Firebase Admin SDK before creating/authenticating users. If not, this is a blocker to merge.


🏁 Script executed:

#!/bin/bash
# Search for the /google-login handler in the server code
rg -n "google-login"
# Search for Firebase Admin SDK token verification calls
rg -n "verifyIdToken"
rg -n "auth().verifyIdToken"
# Search for any use of firebase-admin in server dependencies
grep -R "firebase-admin" -n .

Length of output: 264


🏁 Script executed:

#!/bin/bash
# Display the /google-login handler implementation to verify ID token verification
sed -n '1,200p' backend/routes/auth.js

Length of output: 1851


Critical: Implement Firebase ID Token Verification in google-login Endpoint

The backend currently trusts client-supplied email/googleId without verifying the Firebase ID token. This allows attackers to spoof identities. You must:

• Frontend (src/hooks/firebase.ts)

  • Return the user’s ID token alongside the credential:
    const signInWithGoogle = async () => {
      await setPersistence(auth, browserLocalPersistence);
    - return await signInWithPopup(auth, googleAuthProvider);
    + const cred = await signInWithPopup(auth, googleAuthProvider);
    + const idToken = await cred.user.getIdToken();
    + return { cred, idToken };
    };
  • Send idToken (not raw UID/email) in your POST to /api/auth/google-login.

• Backend (backend/routes/auth.js)

  1. Install & initialize the Firebase Admin SDK:
    // at top of file
    const admin = require('firebase-admin');
    admin.initializeApp({
      credential: admin.credential.cert(require('../path/to/serviceAccountKey.json')),
    });
  2. Verify the ID token in /google-login before trusting any claims:
    router.post("/google-login", async (req, res) => {
      try {
-    const { email, name, googleId } = req.body;
+    const { idToken } = req.body;
+    const decoded = await admin.auth().verifyIdToken(idToken);
+    const { uid, email, name } = decoded;

     let user = await User.findOne({ email });
     if (!user) {
       user = new User({ username: name, email, googleId: uid });
       await user.save();
     }

     return res.json({ message: "Login successful", user });
   } catch (error) {
     console.error("Google login error:", error);
     return res.status(401).json({ message: "Invalid or expired token" });
   }
 });
 ```

Please implement these changes before merging.

🤖 Prompt for AI Agents
In src/hooks/firebase.ts around lines 18-21, you need to return the Firebase ID
token from signInWithGoogle and ensure the frontend sends that idToken (not raw
uid/email) in the POST to /api/auth/google-login; modify signInWithGoogle to
obtain and return user.getIdToken() (or equivalent) alongside the credential so
the caller can include idToken in the request body. On the backend
(backend/routes/auth.js) add and initialize the Firebase Admin SDK (require
firebase-admin, initializeApp with service account credential) near the top of
the file, then in the /google-login handler verify the received idToken with
admin.auth().verifyIdToken(idToken) and only trust claims (uid/email) from the
verified token result before creating or logging in the user; reject requests
with invalid or missing tokens with a 401.


export { auth, signInWithGoogle };
16 changes: 15 additions & 1 deletion src/pages/Login/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import axios from "axios";
import { useNavigate, Link } from "react-router-dom";
import { ThemeContext } from "../../context/ThemeContext";
import type { ThemeContextType } from "../../context/ThemeContext";
import GoogleSignIn from "../../components/GoogleSignIn";



const backendUrl = import.meta.env.VITE_BACKEND_URL;

Expand Down Expand Up @@ -122,12 +125,23 @@ const Login: React.FC = () => {
<button
type="submit"
disabled={isLoading}
className="w-full bg-gradient-to-r from-purple-600 via-pink-600 to-indigo-600 text-white py-4 px-6 rounded-2xl font-semibold focus:ring-4 focus:ring-purple-500/50 transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed"
className="w-full bg-gradient-to-r from-purple-600 via-pink-600 to-indigo-600 text-white py-4 px-6 rounded-2xl font-semibold focus:ring-4 focus:ring-purple-500/50 transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed transform hover:scale-105 shadow-lg"
>
{isLoading ? "Signing in..." : "Sign In"}
</button>
<div className="flex items-center my-4">
<div className="flex-grow h-px bg-gray-300" />
<span className="mx-4 text-gray-500 font-semibold">or</span>
<div className="flex-grow h-px bg-gray-300" />
</div>

{/* Google Sign-In Button */}
<div>
<GoogleSignIn />
</div>
Comment on lines +132 to +141
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Unify post-login navigation and error handling across password and Google flows

Password login navigates to "/home" and surfaces messages in-form; GoogleSignIn (per summary) navigates to "/" and uses alerts. For consistency and better UX, pass callbacks so GoogleSignIn uses the same messaging and destination.

Apply:

-            <div>
-              <GoogleSignIn />
-            </div>
+            <div>
+              <GoogleSignIn
+                onSuccess={() => navigate("/home")}
+                onError={(msg: string) => setMessage(msg || "Something went wrong")}
+              />
+            </div>

Follow-up: update GoogleSignIn to accept optional onSuccess/onError props and to surface backend responses via these instead of alert().

Ensure CORS is configured on the backend to allow the frontend origin (e.g., http://localhost:5173) for the /api/auth/google-login endpoint.


🏁 Script executed:

#!/bin/bash
# Find all occurrences of GoogleSignIn in the codebase
rg "GoogleSignIn" -n src

# Locate the import in Login.tsx
rg "import .*GoogleSignIn" -n src/pages/Login/Login.tsx

# Search for the component definition
rg -n "export (default )?function GoogleSignIn" -n src

Length of output: 624


Unify post-login navigation and error handling across password and Google flows

  • In src/pages/Login/Login.tsx, update the GoogleSignIn usage to pass the same callbacks you use for the password flow:
     <div>
  • <GoogleSignIn
  •  onSuccess={() => navigate("/home")}
    
  •  onError={(msg: string) => setMessage(msg || "Something went wrong")}
    
  • />
``` - In **src/components/GoogleSignIn.tsx**, extend the component’s props to accept optional `onSuccess` and `onError` callbacks. Replace any direct `navigate("/")` or `alert()` calls with these props: - Call `onSuccess()` on a successful sign-in - Call `onError(errorMessage)` on failure - (Optional) Mirror this change in **src/pages/Signup/Signup.tsx** to keep signup and login consistent. - Ensure your backend’s CORS policy allows your frontend origin (e.g., http://localhost:5173) to call `/api/auth/google-login`.
📝 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
<div className="flex items-center my-4">
<div className="flex-grow h-px bg-gray-300" />
<span className="mx-4 text-gray-500 font-semibold">or</span>
<div className="flex-grow h-px bg-gray-300" />
</div>
{/* Google Sign-In Button */}
<div>
<GoogleSignIn />
</div>
<div className="flex items-center my-4">
<div className="flex-grow h-px bg-gray-300" />
<span className="mx-4 text-gray-500 font-semibold">or</span>
<div className="flex-grow h-px bg-gray-300" />
</div>
{/* Google Sign-In Button */}
<div>
<GoogleSignIn
onSuccess={() => navigate("/home")}
onError={(msg: string) => setMessage(msg || "Something went wrong")}
/>
</div>
🤖 Prompt for AI Agents
In src/pages/Login/Login.tsx around lines 132-141, the GoogleSignIn usage needs
to be updated to accept the same post-login navigation and error handling
callbacks as the password flow: change the <GoogleSignIn /> invocation to pass
the existing onSuccess and onError handlers (or the functions used by the
password flow that call navigate("/") and show errors) so both flows behave
identically; in src/components/GoogleSignIn.tsx add optional props onSuccess?:
() => void and onError?: (msg: string) => void, remove any direct navigate("/")
or alert() calls, and instead call onSuccess() on successful sign-in or
onError(errorMessage) on failure; optionally apply the same change to
src/pages/Signup/Signup.tsx for parity; also verify backend CORS permits the
frontend origin (e.g., http://localhost:5173) to call /api/auth/google-login.

</form>


{/* Message */}
{message && (
<div className={`mt-6 p-4 rounded-2xl text-center text-sm font-medium ${
Expand Down
15 changes: 15 additions & 0 deletions src/pages/Signup/Signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React, { useState } from "react";
import axios from "axios";
import { useNavigate ,Link } from "react-router-dom";
import { User, Mail, Lock } from "lucide-react";
import GoogleSignIn from "../../components/GoogleSignIn";

const backendUrl = import.meta.env.VITE_BACKEND_URL;
interface SignUpFormData {
username: string;
Expand Down Expand Up @@ -131,6 +133,18 @@ const navigate = useNavigate();
>
Create Account
</button>

{/* OR separator */}
<div className="flex items-center my-4">
<div className="flex-grow h-px bg-gray-300" />
<span className="mx-4 text-gray-200 font-semibold">or</span>
<div className="flex-grow h-px bg-gray-300" />
</div>

{/* Google Sign-In Button */}
<div>
<GoogleSignIn />
</div>
</div>

{message && (
Expand All @@ -141,6 +155,7 @@ const navigate = useNavigate();
}`}>
{message}
</div>

)}

<div className="text-center mt-8">
Expand Down
4 changes: 4 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
server:{
host:'0.0.0.0',
port:5173
}
Comment on lines +7 to +10
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

Parameterize dev server host/port and avoid binding to all interfaces by default

Binding to 0.0.0.0 exposes your dev server to the LAN. Prefer localhost by default and allow overrides via env for multi-device testing. Also avoids duplicating host config with the "vite --host" script.

Apply this minimal change to make host/port configurable:

   server:{
-    host:'0.0.0.0',
-    port:5173
+    host: process.env.VITE_DEV_HOST ?? 'localhost',
+    port: Number(process.env.VITE_DEV_PORT ?? 5173)
   }

Ensure Firebase Authentication’s Authorized domains include the dev URL you’ll actually visit in the browser (e.g., http://localhost:5173). If you plan to use a LAN IP, add that domain too.


Parameterize Vite Dev Server Host/Port
Avoid binding to 0.0.0.0 by default—use localhost and fall back to env vars for multi-device testing:

// vite.config.ts
 server: {
-  host: '0.0.0.0',
-  port: 5173
+  host: process.env.VITE_DEV_HOST ?? 'localhost',
+  port: Number(process.env.VITE_DEV_PORT ?? 5173),
 }

• This lets you override via VITE_DEV_HOST/VITE_DEV_PORT when you need LAN access, without duplicating the --host CLI flag.
• Don’t forget to add your actual dev URL(s) (e.g. http://localhost:5173 or any LAN IP) to Firebase Authentication’s Authorized Domains.

🤖 Prompt for AI Agents
In vite.config.ts around lines 7 to 10, the dev server is hardcoded to host
'0.0.0.0' and port 5173; change it to use localhost by default and read fallback
values from VITE_DEV_HOST and VITE_DEV_PORT environment variables so developers
only opt into LAN binding when they set the envs. Update the server config to
set host = process.env.VITE_DEV_HOST || 'localhost' and port =
Number(process.env.VITE_DEV_PORT) || 5173 (ensure numeric conversion and a safe
default), and ensure any dev URLs you use are added to Firebase Auth Authorized
Domains when enabling LAN access.

})