auth.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. from datetime import timedelta
  2. from typing import Any
  3. import uuid
  4. import logging
  5. import urllib.parse
  6. from fastapi import APIRouter, Depends, HTTPException, status
  7. from fastapi.responses import RedirectResponse
  8. from fastapi.security import OAuth2PasswordRequestForm
  9. from sqlalchemy.orm import Session
  10. from backend.app.core import security
  11. from backend.app.core.config import settings
  12. from backend.app.core.database import get_db
  13. from backend.app.models import sql_models
  14. from backend.app.schemas import schemas
  15. from backend.app.services.simple_auth import SimpleAuthService
  16. logger = logging.getLogger(__name__)
  17. router = APIRouter()
  18. @router.post("/login", response_model=schemas.Token)
  19. def login_access_token(
  20. db: Session = Depends(get_db),
  21. form_data: OAuth2PasswordRequestForm = Depends()
  22. ) -> Any:
  23. user = db.query(sql_models.User).filter(sql_models.User.username == form_data.username).first()
  24. if not user or not security.verify_password(form_data.password, user.hashed_password):
  25. raise HTTPException(
  26. status_code=status.HTTP_401_UNAUTHORIZED,
  27. detail="Incorrect username or password",
  28. )
  29. access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
  30. access_token = security.create_access_token(
  31. data={"sub": user.username}, expires_delta=access_token_expires
  32. )
  33. return {
  34. "access_token": access_token,
  35. "token_type": "bearer",
  36. "is_superuser": user.is_superuser if hasattr(user, "is_superuser") else False,
  37. "username": user.username
  38. }
  39. @router.get("/auth/simple-callback")
  40. async def simple_auth_callback(
  41. ticket: str,
  42. db: Session = Depends(get_db)
  43. ) -> Any:
  44. """
  45. Callback for Simple Auth Platform.
  46. Receives ticket (GET param), validates it, logs in/creates local user,
  47. and redirects to frontend with token.
  48. """
  49. logger.info(f"Received simple-callback with ticket: {ticket}")
  50. validation_result = await SimpleAuthService.validate_ticket(ticket)
  51. logger.info(f"Ticket validation result: {validation_result}")
  52. if not validation_result or not validation_result.get("valid"):
  53. logger.error(f"Invalid ticket: {ticket}")
  54. raise HTTPException(
  55. status_code=status.HTTP_400_BAD_REQUEST,
  56. detail="Invalid ticket"
  57. )
  58. # Determine local username
  59. # Priority: mapped_key > mobile > mapped_email > user_id
  60. username = validation_result.get("mapped_key")
  61. if not username:
  62. username = validation_result.get("mobile")
  63. if not username:
  64. username = validation_result.get("mapped_email")
  65. if not username:
  66. username = str(validation_result.get("user_id"))
  67. if not username:
  68. logger.error("Could not determine user identity from auth response")
  69. raise HTTPException(
  70. status_code=status.HTTP_400_BAD_REQUEST,
  71. detail="Could not determine user identity from auth response"
  72. )
  73. logger.info(f"Identified user: {username}")
  74. # Check if user exists
  75. user = db.query(sql_models.User).filter(sql_models.User.username == username).first()
  76. if not user:
  77. logger.info(f"User {username} not found. Creating new user.")
  78. # Create new user
  79. # Generate a random password since they use external auth
  80. random_password = uuid.uuid4().hex
  81. hashed_password = security.get_password_hash(random_password)
  82. user = sql_models.User(
  83. username=username,
  84. hashed_password=hashed_password,
  85. is_active=True,
  86. is_superuser=False
  87. )
  88. db.add(user)
  89. db.commit()
  90. db.refresh(user)
  91. logger.info(f"User {username} created successfully.")
  92. else:
  93. logger.info(f"User {username} found.")
  94. # Generate token
  95. access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
  96. access_token = security.create_access_token(
  97. data={"sub": user.username}, expires_delta=access_token_expires
  98. )
  99. # Redirect to frontend root with token in query param
  100. # URL Encode parameters to ensure special characters (like Chinese or +) are handled correctly
  101. encoded_token = urllib.parse.quote(access_token)
  102. encoded_username = urllib.parse.quote(user.username)
  103. is_superuser_str = "true" if user.is_superuser else "false"
  104. redirect_url = f"/?token={encoded_token}&username={encoded_username}&superuser={is_superuser_str}"
  105. logger.info(f"Redirecting to frontend: {redirect_url}")
  106. return RedirectResponse(url=redirect_url)