Merge pull request #20 from AlexisLopez-Dev/feature/backend-v3
Añadido soporte para autenticación con Google y mejoras en la gestión…
This commit is contained in:
@@ -5,6 +5,8 @@ __pycache__/
|
|||||||
|
|
||||||
# Variables de entorno
|
# Variables de entorno
|
||||||
.env
|
.env
|
||||||
|
.env*
|
||||||
|
|
||||||
|
|
||||||
# Configuraciones del editor
|
# Configuraciones del editor
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|||||||
+6
-2
@@ -14,15 +14,19 @@ from api.routers.auth import router as auth_router
|
|||||||
from api.routers.history import router as history_router
|
from api.routers.history import router as history_router
|
||||||
from api.routers.test_mongo import router as test_mongo_router
|
from api.routers.test_mongo import router as test_mongo_router
|
||||||
from api.routers.docit2mf_build import router as docit2mf_router
|
from api.routers.docit2mf_build import router as docit2mf_router
|
||||||
|
from api.routers.google_auth import router as google_auth_router
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
# Aquí podrías hacer comprobaciones si quieres
|
|
||||||
yield
|
yield
|
||||||
# No hace falta cerrar nada con Motor
|
|
||||||
|
|
||||||
app = FastAPI(lifespan=lifespan)
|
app = FastAPI(lifespan=lifespan)
|
||||||
|
|
||||||
|
app.include_router(google_auth_router)
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=["*"],
|
allow_origins=["*"],
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# api/routers/google_auth.py
|
||||||
|
|
||||||
|
from fastapi import APIRouter, HTTPException, Depends
|
||||||
|
from fastapi.responses import RedirectResponse
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from bson import ObjectId
|
||||||
|
import httpx
|
||||||
|
import os
|
||||||
|
import jwt
|
||||||
|
|
||||||
|
from api.database.mongodb import users_collection
|
||||||
|
from api.utils.security import create_access_token
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/auth/google", tags=["auth"])
|
||||||
|
|
||||||
|
|
||||||
|
GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
|
||||||
|
GOOGLE_CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET")
|
||||||
|
REDIRECT_URI = "http://localhost:8000/api/auth/google/callback"
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# 1. LOGIN → REDIRECCIÓN A GOOGLE
|
||||||
|
# -----------------------------
|
||||||
|
@router.get("/login")
|
||||||
|
async def google_login():
|
||||||
|
google_auth_url = (
|
||||||
|
"https://accounts.google.com/o/oauth2/v2/auth"
|
||||||
|
"?response_type=code"
|
||||||
|
f"&client_id={GOOGLE_CLIENT_ID}"
|
||||||
|
f"&redirect_uri={REDIRECT_URI}"
|
||||||
|
"&scope=openid%20email%20profile"
|
||||||
|
"&access_type=offline"
|
||||||
|
"&prompt=consent"
|
||||||
|
)
|
||||||
|
|
||||||
|
return RedirectResponse(google_auth_url)
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# 2. CALLBACK → GOOGLE DEVUELVE EL CODE
|
||||||
|
# -----------------------------
|
||||||
|
@router.get("/callback")
|
||||||
|
async def google_callback(code: str):
|
||||||
|
|
||||||
|
# 1. Intercambiar code por access_token
|
||||||
|
token_url = "https://oauth2.googleapis.com/token"
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"code": code,
|
||||||
|
"client_id": GOOGLE_CLIENT_ID,
|
||||||
|
"client_secret": GOOGLE_CLIENT_SECRET,
|
||||||
|
"redirect_uri": REDIRECT_URI,
|
||||||
|
"grant_type": "authorization_code",
|
||||||
|
}
|
||||||
|
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
token_response = await client.post(token_url, data=data)
|
||||||
|
token_json = token_response.json()
|
||||||
|
|
||||||
|
if "access_token" not in token_json:
|
||||||
|
raise HTTPException(status_code=400, detail="Error obteniendo token de Google")
|
||||||
|
|
||||||
|
access_token = token_json["access_token"]
|
||||||
|
|
||||||
|
# 2. Obtener datos del usuario desde Google
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
userinfo = await client.get(
|
||||||
|
"https://www.googleapis.com/oauth2/v2/userinfo",
|
||||||
|
headers={"Authorization": f"Bearer {access_token}"}
|
||||||
|
)
|
||||||
|
|
||||||
|
user_data = userinfo.json()
|
||||||
|
|
||||||
|
google_id = user_data["id"]
|
||||||
|
email = user_data["email"]
|
||||||
|
name = user_data.get("name", "Usuario")
|
||||||
|
|
||||||
|
# 3. Buscar usuario en MongoDB
|
||||||
|
user = await users_collection.find_one({"email": email})
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
# Crear usuario nuevo
|
||||||
|
new_user = {
|
||||||
|
"username": name,
|
||||||
|
"email": email,
|
||||||
|
"password_hash": None, # No hay contraseña
|
||||||
|
"google_id": google_id,
|
||||||
|
"history": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
result = await users_collection.insert_one(new_user)
|
||||||
|
user_id = result.inserted_id
|
||||||
|
else:
|
||||||
|
user_id = user["_id"]
|
||||||
|
|
||||||
|
# 4. Crear JWT de tu sistema
|
||||||
|
token = create_access_token({"user_id": str(user_id)})
|
||||||
|
|
||||||
|
# 5. Redirigir al frontend con el token
|
||||||
|
return {"message": "Login con Google exitoso", "token": token}
|
||||||
@@ -17,7 +17,7 @@ def verify_password(plain_password: str, hashed_password: str) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def generate_token() -> str:
|
def generate_token() -> str:
|
||||||
return secrets.token_hex(32) # 64 caracteres seguros
|
return secrets.token_hex(32)
|
||||||
|
|
||||||
|
|
||||||
security_scheme = HTTPBearer()
|
security_scheme = HTTPBearer()
|
||||||
@@ -35,6 +35,8 @@ async def get_current_user(
|
|||||||
detail="Token inválido o usuario no autenticado",
|
detail="Token inválido o usuario no autenticado",
|
||||||
)
|
)
|
||||||
|
|
||||||
# devolvemos el documento tal cual (dict)
|
|
||||||
user["id"] = str(user["_id"])
|
user["id"] = str(user["_id"])
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
def create_access_token(data: dict):
|
||||||
|
return jwt.encode(data, SECRET_KEY, algorithm="HS256")
|
||||||
|
|||||||
@@ -6,3 +6,5 @@ passlib
|
|||||||
bcrypt==4.0.1
|
bcrypt==4.0.1
|
||||||
email-validator
|
email-validator
|
||||||
slowapi
|
slowapi
|
||||||
|
httpx
|
||||||
|
PyJWT
|
||||||
|
|||||||
Reference in New Issue
Block a user