import httpx from fastapi import APIRouter, Depends, Request, HTTPException from fastapi.responses import RedirectResponse import os import secrets from app.auth.deps import get_current_user from app.db import SessionLocal from app.models import User from app.auth.jwt import create_jwt_token, create_refresh_token, decode_token router = APIRouter() GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID") GOOGLE_CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET") GOOGLE_REDIRECT_URI = os.getenv("GOOGLE_REDIRECT_URI") 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/v2/userinfo" @router.get("/login/google") def login_google(): state = secrets.token_urlsafe(16) url = ( f"{GOOGLE_AUTH_URL}" f"?client_id={GOOGLE_CLIENT_ID}" f"&redirect_uri={GOOGLE_REDIRECT_URI}" f"&response_type=code" f"&scope=openid email profile" f"&state={state}" ) response = RedirectResponse(url) response.set_cookie( "oauth_state", state, httponly=True, secure=True, samesite="lax" ) return response @router.get("/auth/callback") async def auth_callback(request: Request, code: str, state: str): cookie_state = request.cookies.get("oauth_state") if not cookie_state or cookie_state != state: raise HTTPException(status_code=400, detail="Invalid state") async with httpx.AsyncClient() as client: token_res = await client.post( GOOGLE_TOKEN_URL, data={ "code": code, "client_id": GOOGLE_CLIENT_ID, "client_secret": GOOGLE_CLIENT_SECRET, "redirect_uri": GOOGLE_REDIRECT_URI, "grant_type": "authorization_code", }, ) token_json = token_res.json() access_token = token_json["access_token"] user_res = await client.get( GOOGLE_USERINFO_URL, headers={"Authorization": f"Bearer {access_token}"}, ) user_info = user_res.json() db = SessionLocal() user = db.query(User).filter(User.email == user_info["email"]).first() if not user: user = User( google_id=user_info["id"], email=user_info["email"], name=user_info["name"], avatar=user_info["picture"], ) db.add(user) db.commit() db.refresh(user) # 异步/同步将头像转存到 S3 from app.utils.s3 import upload_google_avatar_to_s3 s3_avatar_url = await upload_google_avatar_to_s3(user_info["picture"], str(user.id)) # 将更新后的 S3 URL 写回数据库 user.avatar = s3_avatar_url db.commit() # 这里不需要再次 refresh,除非后面还要用到更新后的 user 对象 # db.refresh(user) db.close() jwt_token = create_jwt_token({ "sub": user.id, "email": user.email, }) refresh_token = create_refresh_token({ "sub": user.id, "email": user.email, }) return { "access_token": jwt_token, "refresh_token": refresh_token, "token_type": "bearer", "user": { "email": user.email, "name": user.name, "avatar": user.avatar, } } @router.get("/me") def me(user = Depends(get_current_user)): return { "email": user.email, "name": user.name, "avatar": user.avatar, } @router.post("/refresh") def refresh_token(refresh_token: str): payload = decode_token(refresh_token) if payload.get("type") != "refresh": raise HTTPException(status_code=401) user_id = payload.get("sub") new_access = create_jwt_token({"sub": user_id}) return {"access_token": new_access} @router.post("/logout") def logout(): return {"message": "ok"}