123
This commit is contained in:
223
backend/auth_service/app/main.py
Normal file
223
backend/auth_service/app/main.py
Normal file
@@ -0,0 +1,223 @@
|
||||
"""
|
||||
Weibo-HotSign Authentication Service
|
||||
Main FastAPI application entry point
|
||||
"""
|
||||
|
||||
from fastapi import FastAPI, Depends, HTTPException, status, Security
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
import uvicorn
|
||||
import os
|
||||
import logging
|
||||
|
||||
from shared.models import get_db, User
|
||||
from auth_service.app.models.database import create_tables
|
||||
from auth_service.app.schemas.user import (
|
||||
UserCreate, UserLogin, UserResponse, Token, TokenData, RefreshTokenRequest,
|
||||
)
|
||||
from auth_service.app.services.auth_service import AuthService
|
||||
from auth_service.app.utils.security import (
|
||||
verify_password, create_access_token, decode_access_token,
|
||||
create_refresh_token, verify_refresh_token, revoke_refresh_token,
|
||||
)
|
||||
|
||||
# Configure logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Initialize FastAPI app
|
||||
app = FastAPI(
|
||||
title="Weibo-HotSign Authentication Service",
|
||||
description="Handles user authentication and authorization for Weibo-HotSign system",
|
||||
version="1.0.0",
|
||||
docs_url="/docs",
|
||||
redoc_url="/redoc"
|
||||
)
|
||||
|
||||
# CORS middleware configuration
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["http://localhost:3000", "http://localhost:80"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Security scheme for JWT
|
||||
security = HTTPBearer()
|
||||
|
||||
async def get_current_user(
|
||||
credentials: HTTPAuthorizationCredentials = Security(security),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
) -> UserResponse:
|
||||
"""
|
||||
Dependency to get current user from JWT token
|
||||
"""
|
||||
token = credentials.credentials
|
||||
payload = decode_access_token(token)
|
||||
|
||||
if payload is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid or expired token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
user_id = payload.get("sub")
|
||||
if not user_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid token payload",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
auth_service = AuthService(db)
|
||||
user = await auth_service.get_user_by_id(user_id)
|
||||
|
||||
if user is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="User not found",
|
||||
)
|
||||
|
||||
if not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="User account is deactivated",
|
||||
)
|
||||
|
||||
return UserResponse.from_orm(user)
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
"""Initialize database tables on startup"""
|
||||
await create_tables()
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {
|
||||
"service": "Weibo-HotSign Authentication Service",
|
||||
"status": "running",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
return {"status": "healthy"}
|
||||
|
||||
@app.post("/auth/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def register_user(user_data: UserCreate, db: AsyncSession = Depends(get_db)):
|
||||
"""
|
||||
Register a new user account
|
||||
"""
|
||||
auth_service = AuthService(db)
|
||||
|
||||
# Check if user already exists - optimized with single query
|
||||
email_user, username_user = await auth_service.check_user_exists(user_data.email, user_data.username)
|
||||
|
||||
if email_user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="User with this email already exists"
|
||||
)
|
||||
|
||||
if username_user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="Username already taken"
|
||||
)
|
||||
|
||||
# Create new user
|
||||
try:
|
||||
user = await auth_service.create_user(user_data)
|
||||
return UserResponse.from_orm(user)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to create user: {str(e)}"
|
||||
)
|
||||
|
||||
@app.post("/auth/login", response_model=Token)
|
||||
async def login_user(login_data: UserLogin, db: AsyncSession = Depends(get_db)):
|
||||
"""
|
||||
Authenticate user and return JWT token
|
||||
"""
|
||||
auth_service = AuthService(db)
|
||||
|
||||
# Find user by email
|
||||
user = await auth_service.get_user_by_email(login_data.email)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid email or password"
|
||||
)
|
||||
|
||||
# Verify password
|
||||
if not verify_password(login_data.password, user.hashed_password):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid email or password"
|
||||
)
|
||||
|
||||
# Check if user is active
|
||||
if not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="User account is deactivated"
|
||||
)
|
||||
|
||||
# Create access token
|
||||
access_token = create_access_token(data={"sub": str(user.id), "username": user.username})
|
||||
|
||||
# Create refresh token (stored in Redis)
|
||||
refresh_token = await create_refresh_token(str(user.id))
|
||||
|
||||
return Token(
|
||||
access_token=access_token,
|
||||
refresh_token=refresh_token,
|
||||
token_type="bearer",
|
||||
expires_in=3600 # 1 hour
|
||||
)
|
||||
|
||||
@app.post("/auth/refresh", response_model=Token)
|
||||
async def refresh_token(body: RefreshTokenRequest, db: AsyncSession = Depends(get_db)):
|
||||
"""
|
||||
Exchange a valid refresh token for a new access + refresh token pair (Token Rotation).
|
||||
The old refresh token is revoked immediately.
|
||||
"""
|
||||
# Verify the incoming refresh token
|
||||
user_id = await verify_refresh_token(body.refresh_token)
|
||||
if user_id is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid or expired refresh token",
|
||||
)
|
||||
|
||||
# Ensure the user still exists and is active
|
||||
auth_service = AuthService(db)
|
||||
user = await auth_service.get_user_by_id(user_id)
|
||||
if user is None or not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="User not found or deactivated",
|
||||
)
|
||||
|
||||
# Revoke old token, issue new pair
|
||||
await revoke_refresh_token(body.refresh_token)
|
||||
new_access = create_access_token(data={"sub": str(user.id), "username": user.username})
|
||||
new_refresh = await create_refresh_token(str(user.id))
|
||||
|
||||
return Token(
|
||||
access_token=new_access,
|
||||
refresh_token=new_refresh,
|
||||
token_type="bearer",
|
||||
expires_in=3600,
|
||||
)
|
||||
|
||||
@app.get("/auth/me", response_model=UserResponse)
|
||||
async def get_current_user_info(current_user: UserResponse = Depends(get_current_user)):
|
||||
"""
|
||||
Get current user information
|
||||
"""
|
||||
return current_user
|
||||
Reference in New Issue
Block a user