""" 认证 API """ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.api.deps import get_current_user from app.models.user import User from app.schemas.auth import ( RegisterRequest, LoginRequest, LoginResponse, RefreshTokenRequest, RefreshTokenResponse, UserResponse, ) from app.services.auth import ( get_user_by_email, get_user_by_phone, get_user_by_id, create_user, authenticate_user, create_access_token, create_refresh_token, update_refresh_token, decode_token, get_user_organization_info, ) router = APIRouter(prefix="/auth", tags=["认证"]) @router.post("/register", response_model=LoginResponse, status_code=status.HTTP_201_CREATED) async def register( request: RegisterRequest, db: AsyncSession = Depends(get_db), ): """ 用户注册 - 支持邮箱或手机号注册(至少提供一个) - 注册后自动登录,返回 Token """ # 验证至少提供邮箱或手机号 if not request.email and not request.phone: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="请提供邮箱或手机号", ) # 检查邮箱是否已存在 if request.email: existing = await get_user_by_email(db, request.email) if existing: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="该邮箱已被注册", ) # 检查手机号是否已存在 if request.phone: existing = await get_user_by_phone(db, request.phone) if existing: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="该手机号已被注册", ) # 创建用户 user = await create_user( db=db, email=request.email, phone=request.phone, password=request.password, name=request.name, role=request.role, ) # 生成 Token access_token = create_access_token(user.id) refresh_token, refresh_expires_at = create_refresh_token(user.id) # 保存 refresh token await update_refresh_token(db, user, refresh_token, refresh_expires_at) await db.commit() # 获取组织信息 org_info = await get_user_organization_info(db, user) return LoginResponse( access_token=access_token, refresh_token=refresh_token, user=UserResponse( id=user.id, email=user.email, phone=user.phone, name=user.name, avatar=user.avatar, role=user.role, is_verified=user.is_verified, **org_info, ), ) @router.post("/login", response_model=LoginResponse) async def login( request: LoginRequest, db: AsyncSession = Depends(get_db), ): """ 用户登录 - 支持邮箱+密码 或 手机号+密码 登录 - 返回 accessToken 和 refreshToken """ # 验证请求参数 if not request.email and not request.phone: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="请提供邮箱或手机号", ) if not request.password: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="请提供密码", ) # 验证用户 user = await authenticate_user( db=db, email=request.email, phone=request.phone, password=request.password, ) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="邮箱/手机号或密码错误", ) if not user.is_active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="账号已被禁用", ) # 生成 Token access_token = create_access_token(user.id) refresh_token, refresh_expires_at = create_refresh_token(user.id) # 保存 refresh token await update_refresh_token(db, user, refresh_token, refresh_expires_at) await db.commit() # 获取组织信息 org_info = await get_user_organization_info(db, user) return LoginResponse( access_token=access_token, refresh_token=refresh_token, user=UserResponse( id=user.id, email=user.email, phone=user.phone, name=user.name, avatar=user.avatar, role=user.role, is_verified=user.is_verified, **org_info, ), ) @router.post("/refresh", response_model=RefreshTokenResponse) async def refresh_token( request: RefreshTokenRequest, db: AsyncSession = Depends(get_db), ): """ 刷新 Access Token - 使用 refreshToken 获取新的 accessToken - refreshToken 有效期 7 天 """ # 解码 refresh token payload = decode_token(request.refresh_token) if not payload: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="无效的 refresh token", ) if payload.get("type") != "refresh": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="无效的 token 类型", ) user_id = payload.get("sub") if not user_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="无效的 token", ) # 获取用户 user = await get_user_by_id(db, user_id) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="用户不存在", ) # 验证 refresh token 是否匹配 if user.refresh_token != request.refresh_token: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="refresh token 已失效", ) if not user.is_active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="账号已被禁用", ) # 生成新的 access token access_token = create_access_token(user.id) return RefreshTokenResponse(access_token=access_token) @router.post("/logout") async def logout( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """ 退出登录 - 清除 refresh token,使其失效 """ current_user.refresh_token = None current_user.refresh_token_expires_at = None await db.commit() return {"message": "已退出登录"}