"""认证 API""" from fastapi import APIRouter, Depends, HTTPException, Request, status from fastapi.security import OAuth2PasswordRequestForm from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import or_ from sqlalchemy.future import select from ..core.database import get_db from ..models import User, UserStatus from ..schemas import PhoneCodeSendRequest, PhoneCodeVerifyRequest, UserLoginResponse, UserRegisterRequest from ..services.auth import AuthService from ..services.phone_auth_service import send_phone_verification_code, verify_phone_and_login from ..services.platform_access_service import get_platform_context from ..services.workspace_service import ( ensure_active_workspace_membership, ensure_personal_workspace, list_user_workspaces, register_user_with_personal_workspace, ) router = APIRouter(prefix="/auth", tags=["auth"]) @router.post("/login", response_model=UserLoginResponse) async def login( credentials: OAuth2PasswordRequestForm = Depends(), db: AsyncSession = Depends(get_db), ): """用户登录,返回 JWT""" result = await db.execute( select(User).where(or_(User.username == credentials.username, User.phone_e164 == credentials.username)) ) user = result.scalar_one_or_none() if not user or not AuthService.verify_password( credentials.password, user.password_hash ): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password", headers={"WWW-Authenticate": "Bearer"}, ) if not getattr(user, "is_active", True) or user.status != UserStatus.ACTIVE.value: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="User is disabled", ) await ensure_personal_workspace(db, user=user, commit=False) await ensure_active_workspace_membership(db, user=user, commit=False) await db.commit() access_token = AuthService.create_access_token(data={"sub": user.username}) workspaces = await list_user_workspaces(db, user=user) platform_context = await get_platform_context(db, user=user) return UserLoginResponse( access_token=access_token, token_type="bearer", user={**user.to_dict(), "workspace_context": workspaces, "platform_context": platform_context}, ) @router.post("/phone/send-code") async def send_phone_code( body: PhoneCodeSendRequest, request: Request, db: AsyncSession = Depends(get_db), ): """发送手机号验证码。开发环境会返回 dev_code,生产环境接短信服务商后不返回验证码。""" return await send_phone_verification_code( db, country_code=body.phone_country_code, phone_number=body.phone_number, purpose=body.purpose, request_ip=request.client.host if request.client else None, ) @router.post("/phone/verify", response_model=UserLoginResponse) async def verify_phone_code_login( body: PhoneCodeVerifyRequest, db: AsyncSession = Depends(get_db), ): """手机号验证码登录/自注册,并可绑定邀请。""" result = await verify_phone_and_login( db, country_code=body.phone_country_code, phone_number=body.phone_number, code=body.code, purpose=body.purpose, invite_token=body.invite_token, name=body.name, ) return UserLoginResponse( access_token=result["access_token"], token_type=result["token_type"], user=result["user"], ) @router.post("/register", response_model=UserLoginResponse) async def register( body: UserRegisterRequest, db: AsyncSession = Depends(get_db), ): """注册账号并自动创建个人空间。""" user = await register_user_with_personal_workspace( db, username=body.username, password=body.password, name=body.name, email=body.email or f"{body.username}@placeholder.local", ) access_token = AuthService.create_access_token(data={"sub": user.username}) workspaces = await list_user_workspaces(db, user=user) platform_context = await get_platform_context(db, user=user) return UserLoginResponse( access_token=access_token, token_type="bearer", user={**user.to_dict(), "workspace_context": workspaces, "platform_context": platform_context}, )