Repository navigation
-Added complete Google OAuth flow for login and signup #72
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
Changes from all commits
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 |
|---|---|---|
|
|
@@ -62,7 +62,6 @@ backend/__pycache__/ | |
| *.py[cod] | ||
| *.pyo | ||
| backend/coverage_html/ | ||
|
|
||
| # Compiled binaries | ||
| *.so | ||
| *.dll | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,7 @@ class User(Base): | |
| id = Column(Integer, primary_key=True, index=True) | ||
| username = Column(String, unique=True, index=True, nullable=False) | ||
| email = Column(String, unique=True, index=True, nullable=False) | ||
| hashed_password = Column(String, nullable=False) | ||
| hashed_password = Column(String, nullable=True) | ||
|
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 chainReconsider making hashed_password nullable. Integrity compromised. Current flow always sets a random password for Google users. Keep NOT NULL and migrate existing rows, or justify nulls and harden all auth paths accordingly. - hashed_password = Column(String, nullable=True)
+ hashed_password = Column(String, nullable=False)🏁 Script executed: #!/bin/bash
# Scan for places assuming non-null hashed_password or comparing to None.
rg -nP --type=py -C2 '\bhashed_password\b'Length of output: 7200 Make hashed_password NOT NULL and backfill existing rows Auth paths assume a password exists (verify_password uses user.hashed_password; Google flow writes a hashed password). Backfill any NULL hashed_password rows (generate secure random passwords and hash them, or set an unusable hash + force reset) before altering the column to NOT NULL. - hashed_password = Column(String, nullable=True)
+ hashed_password = Column(String, nullable=False)Locations: backend/models/user.py; backend/services/AuthHandler.py (verify_password usage); backend/routes/google_auth.py (hashed_password assignment); backend/tests/* (fixtures may need updates).
|
||
| is_active = Column(Boolean, default=True) | ||
| is_verified = Column(Boolean, default=False) # Tracks email verification status | ||
| otp = Column(String, nullable=True) # Stores the OTP temporarily | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,68 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from fastapi import APIRouter, Request, Depends, HTTPException | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from fastapi.responses import RedirectResponse | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from authlib.integrations.starlette_client import OAuth | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from starlette.config import Config | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from starlette.requests import Request | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from sqlalchemy.orm import Session | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from core.dependencies import get_db | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from models.user import User | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from services.AuthHandler import AuthHandler | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from core.security import create_access_token | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| router = APIRouter() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+13
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. 🧹 Nitpick Tidy imports. Remove duplicates/unused.
-from fastapi import APIRouter, Request, Depends, HTTPException
-from fastapi.responses import RedirectResponse
-from authlib.integrations.starlette_client import OAuth
-from starlette.config import Config
-from starlette.requests import Request
-from sqlalchemy.orm import Session
-from core.dependencies import get_db
-from models.user import User
-from services.AuthHandler import AuthHandler
-from core.security import create_access_token
-import os
+from fastapi import APIRouter, Request, Depends, HTTPException
+from fastapi.responses import RedirectResponse
+from authlib.integrations.starlette_client import OAuth
+from starlette.config import Config
+from sqlalchemy.orm import Session
+from sqlalchemy import exists
+from core.dependencies import get_db
+from models.user import User
+from core.security import create_access_token
+import os📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| config_data = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "GOOGLE_CLIENT_ID": os.getenv("GOOGLE_CLIENT_ID"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "GOOGLE_CLIENT_SECRET": os.getenv("GOOGLE_CLIENT_SECRET"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "SECRET_KEY": os.getenv("SECRET_KEY", "secret"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| config = Config(environ=config_data) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| oauth = OAuth(config) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+15
to
+21
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. 🛠️ Refactor suggestion Validate env at startup. Fail fast if missing. Ensure client id/secret present to avoid runtime 500s. config_data = {
"GOOGLE_CLIENT_ID": os.getenv("GOOGLE_CLIENT_ID"),
"GOOGLE_CLIENT_SECRET": os.getenv("GOOGLE_CLIENT_SECRET"),
- "SECRET_KEY": os.getenv("SECRET_KEY", "secret"),
}
config = Config(environ=config_data)
+if not config_data["GOOGLE_CLIENT_ID"] or not config_data["GOOGLE_CLIENT_SECRET"]:
+ raise RuntimeError("GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET must be set")📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| oauth.register( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name='google', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client_id=config_data["GOOGLE_CLIENT_ID"], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client_secret=config_data["GOOGLE_CLIENT_SECRET"], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| server_metadata_url='https://accounts.google.com/.well-known/openid-configuration', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client_kwargs={'scope': 'openid email profile'}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @router.get("/login") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async def google_login(request: Request): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| redirect_uri = os.getenv("GOOGLE_REDIRECT_URI") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return await oauth.google.authorize_redirect(request, redirect_uri) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+30
to
+34
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. 🛠️ Refactor suggestion Compute redirect URI via url_for. Robustness++ Avoid hardcoding GOOGLE_REDIRECT_URI; derive from routing. @router.get("/login")
async def google_login(request: Request):
- redirect_uri = os.getenv("GOOGLE_REDIRECT_URI")
+ # Build absolute callback URL under current host
+ redirect_uri = str(request.url_for("google_callback"))
return await oauth.google.authorize_redirect(request, redirect_uri)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @router.get("/callback") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async def google_callback(request: Request, db: Session = Depends(get_db)): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| token = await oauth.google.authorize_access_token(request) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print("Google token response:", token) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user_info = token.get("userinfo") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not user_info: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise HTTPException(status_code=400, detail="No userinfo in Google response") # Find or create user | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+35
to
+41
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. Do not print tokens. Retrieve user info correctly.
async def google_callback(request: Request, db: Session = Depends(get_db)):
- token = await oauth.google.authorize_access_token(request)
- print("Google token response:", token)
- user_info = token.get("userinfo")
+ token = await oauth.google.authorize_access_token(request)
+ # Parse verified claims from ID token
+ user_info = await oauth.google.parse_id_token(request, token)
if not user_info:
raise HTTPException(status_code=400, detail="No userinfo in Google response") # Find or create user📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user = db.query(User).filter(User.email == user_info["email"]).first() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not user: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from core.security import hash_password | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Generate a strong random password for Google OAuth users | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # They won't use this password since they'll always login via Google | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import secrets | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| random_password = secrets.token_urlsafe(16) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hashed_password = hash_password(random_password) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Create username from email and make it unique with a random suffix | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| base_username = user_info["email"].split("@")[0] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| username = f"{base_username}{secrets.token_hex(3)}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user = User( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| username=username, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| email=user_info["email"], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hashed_password=hashed_password, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| is_verified=True, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| profile_completed=False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| db.add(user) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| db.commit() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| db.refresh(user)# Generate JWT token | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+42
to
+64
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. 🛠️ Refactor suggestion Username collision + email_verified handling + DB error path. Guarantee unique username deterministically; honor Google’s email_verified; rollback on failures. - user = db.query(User).filter(User.email == user_info["email"]).first()
+ email = user_info.get("email")
+ if not email:
+ raise HTTPException(status_code=400, detail="Google profile missing email")
+ user = db.query(User).filter(User.email == email).first()
if not user:
- from core.security import hash_password
+ from core.security import hash_password
# Generate a strong random password for Google OAuth users
# They won't use this password since they'll always login via Google
import secrets
random_password = secrets.token_urlsafe(16)
hashed_password = hash_password(random_password)
# Create username from email and make it unique with a random suffix
- base_username = user_info["email"].split("@")[0]
- username = f"{base_username}{secrets.token_hex(3)}"
+ base_username = email.split("@")[0]
+ candidate = base_username
+ suffix = 0
+ while db.query(exists().where(User.username == candidate)).scalar():
+ suffix += 1
+ candidate = f"{base_username}{suffix}"
+ username = candidate
user = User(
username=username,
- email=user_info["email"],
+ email=email,
hashed_password=hashed_password,
- is_verified=True,
+ is_verified=bool(user_info.get("email_verified", True)),
profile_completed=False,
)
- db.add(user)
- db.commit()
- db.refresh(user)# Generate JWT token
+ try:
+ db.add(user)
+ db.commit()
+ db.refresh(user)
+ except Exception:
+ db.rollback()
+ raise📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| access_token = create_access_token({"sub": user.username}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Redirect to frontend with token as query param | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| frontend_url = os.getenv("FRONTEND_URL", "http://localhost:5173") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return RedirectResponse(f"{frontend_url}/login?token={access_token}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+65
to
+68
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. Avoid sending JWT in query string. Use fragment or HttpOnly cookie. Query params leak via Referer. Prefer:
Pick one and align frontend. - access_token = create_access_token({"sub": user.username})
- # Redirect to frontend with token as query param
- frontend_url = os.getenv("FRONTEND_URL", "http://localhost:5173")
- return RedirectResponse(f"{frontend_url}/login?token={access_token}")
+ access_token = create_access_token({"sub": user.username})
+ frontend_url = os.getenv("FRONTEND_URL", "http://localhost:5173")
+ # Option A: fragment to avoid Referer leakage
+ return RedirectResponse(f"{frontend_url}/login#token={access_token}")
+ # Option B (preferred in prod): set HttpOnly cookie and redirect without token
+ # resp = RedirectResponse(f"{frontend_url}/dashboard/posts")
+ # resp.set_cookie("access_token", access_token, httponly=True, samesite="lax", secure=True, max_age=12*3600)
+ # return resp📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -14,6 +14,9 @@ import VerifyEmail from "./pages/VerifyEmail"; | |||||
| import ResetPassword from "./pages/ResetPassword"; | ||||||
| import ForgotPassword from "./pages/ForgotPassword"; | ||||||
|
|
||||||
|
|
||||||
| const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID; | ||||||
|
|
||||||
|
Comment on lines
+17
to
+19
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. 🧹 Nitpick Remove unused GOOGLE_CLIENT_ID. Minimalism engaged. Dead code. Delete to avoid confusion. -const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| function App() { | ||||||
| return ( | ||||||
| <Router> | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,8 +2,12 @@ import React from "react"; | |||||||||||
| import ReactDOM from "react-dom/client"; | ||||||||||||
| import App from "./App"; // Ensure this path is correct | ||||||||||||
| import './index.css'; | ||||||||||||
| import { GoogleOAuthProvider } from '@react-oauth/google'; | ||||||||||||
|
|
||||||||||||
| const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID; | ||||||||||||
|
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. 🧹 Nitpick Guard missing client ID to fail fast. Emit a warning if VITE_GOOGLE_CLIENT_ID is absent. -const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID;
+const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID;
+if (!GOOGLE_CLIENT_ID) {
+ console.warn("VITE_GOOGLE_CLIENT_ID is missing. Google login will not work.");
+}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||
|
|
||||||||||||
| ReactDOM.createRoot(document.getElementById("root")).render( | ||||||||||||
| <App /> | ||||||||||||
| <GoogleOAuthProvider clientId={GOOGLE_CLIENT_ID}> | ||||||||||||
| <App /> | ||||||||||||
| </GoogleOAuthProvider> | ||||||||||||
| ); | ||||||||||||
|
Comment on lines
+5
to
13
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 chainDecide the flow: backend-redirect OR One‑Tap. Do not mix. If you keep server‑redirect, you do not need GoogleOAuthProvider at all. If you keep One‑Tap, do not redirect in Login.jsx. Pick one. Do you plan to use server‑redirect (Authlib) exclusively? If yes, I will generate a cleanup patch to remove the provider and dependency. Affirmative. Initiating code inspection. 🏁 Script executed: #!/bin/bash
# Locate Login.jsx in frontend source
LOGIN_FILE=$(find frontend/src -type f -name "Login.jsx" | head -n1)
echo "Login file: $LOGIN_FILE"
# Inspect for client-side redirect usage
if [[ -n "$LOGIN_FILE" ]]; then
rg -n "redirect" "$LOGIN_FILE" || echo "No 'redirect' usage found"
rg -n "location" "$LOGIN_FILE" || echo "No 'location' usage found"
else
echo "Login.jsx not found"
fi
# Check for One-Tap references and provider imports
rg -n "OneTap" -tjs frontend/src || echo "No OneTap references found"
rg -n "@react-oauth/google" -tjs frontend/src || echo "No provider import references found"Length of output: 639 Choose one auth flow: backend-redirect OR One‑Tap — do not mix. Detected mixing in repo:
Action required:
I will generate a cleanup patch to remove the provider and client One‑Tap code if you confirm server-redirect only. 🤖 Prompt for AI Agents |
||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -4,6 +4,7 @@ import { login } from "../api/auth"; | |||||
| import axios from "axios"; | ||||||
| import { useAuth } from "../context/AuthContext"; | ||||||
| import Navbar from "../components/Navbar"; | ||||||
| import { GoogleLogin, googleLogout } from '@react-oauth/google'; | ||||||
|
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. 🧹 Nitpick Remove unused import. googleLogout is not used. -import { GoogleLogin, googleLogout } from '@react-oauth/google';
+import { GoogleLogin } from '@react-oauth/google';📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
|
|
||||||
| const Login = () => { | ||||||
| const [formData, setFormData] = useState({ username: "", password: "" }); | ||||||
|
|
@@ -14,12 +15,35 @@ const Login = () => { | |||||
| const { login: authLogin } = useAuth(); | ||||||
| const location = useLocation(); | ||||||
|
|
||||||
| const handleGoogleSuccess = async (credentialResponse) => { | ||||||
| try { | ||||||
| // Option 1: Use backend OAuth flow (recommended) | ||||||
| window.location.href = `${import.meta.env.VITE_API_URL}/auth/google/login`; | ||||||
|
|
||||||
| // Option 2: If using Google One Tap and want to POST token to backend: | ||||||
| // const { credential } = credentialResponse; | ||||||
| // const res = await axios.post(`${import.meta.env.VITE_API_URL}/auth/google/onetap`, { token: credential }); | ||||||
| // Handle login as usual with res.data.access_token | ||||||
| } catch (error) { | ||||||
| setError("Google login failed"); | ||||||
| } | ||||||
| }; | ||||||
|
Comment on lines
+18
to
+30
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. Flow mismatch: GoogleLogin onSuccess triggers server redirect. Illogical. Pick one:
Current mix will misfire. Recommend server‑redirect cleanup below. - const handleGoogleSuccess = async (credentialResponse) => {
- try {
- // Option 1: Use backend OAuth flow (recommended)
- window.location.href = `${import.meta.env.VITE_API_URL}/auth/google/login`;
-
- // Option 2: If using Google One Tap and want to POST token to backend:
- // const { credential } = credentialResponse;
- // const res = await axios.post(`${import.meta.env.VITE_API_URL}/auth/google/onetap`, { token: credential });
- // Handle login as usual with res.data.access_token
- } catch (error) {
- setError("Google login failed");
- }
- };
+ // If using server-redirect, no handler needed here.
🤖 Prompt for AI Agents |
||||||
| useEffect(() => { | ||||||
| if (location.state?.fromVerification) { | ||||||
| setSuccess("Email verified! Please log in to continue."); | ||||||
| } | ||||||
| }, [location.state]); | ||||||
|
|
||||||
| useEffect(() => { | ||||||
| const params = new URLSearchParams(window.location.search); | ||||||
| const token = params.get("token"); | ||||||
| if (token) { | ||||||
| localStorage.setItem("token", token); | ||||||
| // Optionally fetch user and redirect | ||||||
| window.location.href = "/dashboard/posts"; | ||||||
| } | ||||||
| }, []); | ||||||
|
Comment on lines
+37
to
+45
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. 🧹 Nitpick Token via query string works, but leaks via Referer. Safer options:
Front/back must align. Do you prefer fragment or cookie? I can patch both sides accordingly. 🤖 Prompt for AI Agents |
||||||
|
|
||||||
| const handleChange = (e) => { | ||||||
| setFormData({ ...formData, [e.target.name]: e.target.value }); | ||||||
| }; | ||||||
|
|
@@ -101,13 +125,14 @@ const Login = () => { | |||||
| type="button" | ||||||
| className="w-full flex items-center justify-center gap-2 border border-gray-300 rounded-full py-2 mb-3 hover:bg-gray-100" | ||||||
| > | ||||||
| <img | ||||||
| src="/education-svgrepo-com.svg" | ||||||
| alt="University Mail Icon" | ||||||
| className="w-6 h-5" | ||||||
|
|
||||||
| <GoogleLogin | ||||||
| onSuccess={handleGoogleSuccess} | ||||||
| onError={() => setError("Google login failed")} | ||||||
| useOneTap | ||||||
| /> | ||||||
| <span>Continue with University mail</span> | ||||||
| </button> | ||||||
|
|
||||||
|
|
||||||
| <div className="flex items-center mb-4"> | ||||||
| <hr className="flex-grow border-gray-300" /> | ||||||
|
|
||||||
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.
Hardcoded session secret. Danger detected.
Use env + secure cookie flags. This backs Authlib state protection.
📝 Committable suggestion
🤖 Prompt for AI Agents