first commit

This commit is contained in:
Anurag Jangid 2026-10-08 02:50:35 +05:30
commit 7a72438fd1
74 changed files with 7548 additions and 0 deletions

130
backend/app/routers/auth.py Normal file
View file

@ -0,0 +1,130 @@
"""
Google OAuth 2.0 flow:
1. GET /api/auth/google/login → redirect to Google consent screen
2. GET /api/auth/google/callback → exchange code for tokens → get user info
→ upsert user in MongoDB → issue JWT
→ redirect to frontend with token
3. GET /api/auth/me → return current user (protected)
4. POST /api/auth/logout → client-side only (stateless JWT), just 200
"""
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi.responses import RedirectResponse, JSONResponse
import httpx
from datetime import datetime, timezone
from app.config import get_settings
from app.database import get_db
from app.dependencies import create_access_token, get_current_user
from app.models.user import UserOut
router = APIRouter(prefix="/api/auth", tags=["auth"])
settings = get_settings()
GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"
GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"
GOOGLE_USERINFO_URL = "https://www.googleapis.com/oauth2/v3/userinfo"
SCOPES = "openid email profile"
# ── Step 1: Redirect to Google ─────────────────────────────────
@router.get("/google/login")
async def google_login():
redirect_uri = f"{settings.backend_url}/api/auth/google/callback"
params = (
f"?client_id={settings.google_client_id}"
f"&redirect_uri={redirect_uri}"
f"&response_type=code"
f"&scope={SCOPES}"
f"&access_type=offline"
f"&prompt=select_account"
)
return RedirectResponse(url=GOOGLE_AUTH_URL + params)
# ── Step 2: Callback from Google ───────────────────────────────
@router.get("/google/callback")
async def google_callback(code: str, db=Depends(get_db)):
redirect_uri = f"{settings.backend_url}/api/auth/google/callback"
# Exchange authorization code for tokens
async with httpx.AsyncClient() as client:
token_response = await client.post(
GOOGLE_TOKEN_URL,
data={
"code": code,
"client_id": settings.google_client_id,
"client_secret": settings.google_client_secret,
"redirect_uri": redirect_uri,
"grant_type": "authorization_code",
},
)
if token_response.status_code != 200:
error_url = f"{settings.frontend_url}/login?error=google_token_failed"
return RedirectResponse(url=error_url)
tokens = token_response.json()
access_token = tokens.get("access_token")
# Fetch user profile from Google
async with httpx.AsyncClient() as client:
userinfo_response = await client.get(
GOOGLE_USERINFO_URL,
headers={"Authorization": f"Bearer {access_token}"},
)
if userinfo_response.status_code != 200:
error_url = f"{settings.frontend_url}/login?error=google_userinfo_failed"
return RedirectResponse(url=error_url)
google_user = userinfo_response.json()
google_id = google_user.get("sub")
email = google_user.get("email", "")
name = google_user.get("name", "")
picture = google_user.get("picture", "")
# Upsert user in MongoDB
now = datetime.now(timezone.utc)
await db["users"].update_one(
{"google_id": google_id},
{
"$set": {
"email": email,
"name": name,
"picture": picture,
"last_login": now,
},
"$setOnInsert": {
"google_id": google_id,
"created_at": now,
},
},
upsert=True,
)
# Issue JWT
jwt_token = create_access_token(
data={"sub": google_id, "email": email, "name": name, "picture": picture}
)
# Redirect to frontend with token
frontend_redirect = f"{settings.frontend_url}/auth/callback?token={jwt_token}"
return RedirectResponse(url=frontend_redirect)
# ── Get current user (protected) ──────────────────────────────
@router.get("/me", response_model=UserOut)
async def get_me(current_user: dict = Depends(get_current_user)):
return UserOut(
google_id=current_user["google_id"],
email=current_user["email"],
name=current_user["name"],
picture=current_user.get("picture"),
)
# ── Logout (stateless — client drops the JWT) ─────────────────
@router.post("/logout")
async def logout():
return JSONResponse({"message": "Logged out successfully"})