"""认证路由""" import hmac from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from pydantic import BaseModel from typing import Optional, List from sqlalchemy.orm import Session from ..database import get_db from ..services.auth import ( authenticate_user, create_access_token, decode_token, update_last_login, hash_password, TokenData, UserInfo ) from ..services.crypto import decrypt_config from ..models.user import User from ..models.tenant_app import TenantApp from ..models.tenant_wechat_app import TenantWechatApp router = APIRouter(prefix="/auth", tags=["认证"]) security = HTTPBearer() class LoginRequest(BaseModel): """登录请求""" username: str password: str class LoginResponse(BaseModel): """登录响应""" success: bool token: Optional[str] = None user: Optional[UserInfo] = None error: Optional[str] = None class ChangePasswordRequest(BaseModel): """修改密码请求""" old_password: str new_password: str # 权限依赖 async def get_current_user( credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db) ) -> User: """获取当前用户""" token = credentials.credentials token_data = decode_token(token) if not token_data: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Token 无效或已过期" ) user = db.query(User).filter(User.id == token_data.user_id).first() if not user or user.status != 1: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="用户不存在或已禁用" ) return user async def require_admin(user: User = Depends(get_current_user)) -> User: """要求管理员权限""" if user.role != 'admin': raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="需要管理员权限" ) return user async def require_operator(user: User = Depends(get_current_user)) -> User: """要求操作员以上权限""" if user.role not in ('admin', 'operator'): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="需要操作员以上权限" ) return user # API 端点 @router.post("/login", response_model=LoginResponse) async def login(request: LoginRequest, db: Session = Depends(get_db)): """用户登录""" user = authenticate_user(db, request.username, request.password) if not user: return LoginResponse(success=False, error="用户名或密码错误") # 更新登录时间 update_last_login(db, user.id) # 生成 Token token = create_access_token({ "user_id": user.id, "username": user.username, "role": user.role }) return LoginResponse( success=True, token=token, user=UserInfo( id=user.id, username=user.username, nickname=user.nickname, role=user.role ) ) @router.get("/me", response_model=UserInfo) async def get_me(user: User = Depends(get_current_user)): """获取当前用户信息""" return UserInfo( id=user.id, username=user.username, nickname=user.nickname, role=user.role ) @router.post("/change-password") async def change_password( request: ChangePasswordRequest, user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """修改密码""" from ..services.auth import verify_password if not verify_password(request.old_password, user.password_hash): raise HTTPException(status_code=400, detail="原密码错误") new_hash = hash_password(request.new_password) db.query(User).filter(User.id == user.id).update({"password_hash": new_hash}) db.commit() return {"success": True, "message": "密码修改成功"} @router.get("/users") async def list_users( user: User = Depends(require_admin), db: Session = Depends(get_db) ): """获取用户列表(仅管理员)""" users = db.query(User).all() return [ { "id": u.id, "username": u.username, "nickname": u.nickname, "role": u.role, "status": u.status, "last_login_at": u.last_login_at, "created_at": u.created_at } for u in users ] class CreateUserRequest(BaseModel): username: str password: str nickname: Optional[str] = None role: str = "viewer" @router.post("/users") async def create_user( request: CreateUserRequest, user: User = Depends(require_admin), db: Session = Depends(get_db) ): """创建用户(仅管理员)""" # 检查用户名是否存在 exists = db.query(User).filter(User.username == request.username).first() if exists: raise HTTPException(status_code=400, detail="用户名已存在") new_user = User( username=request.username, password_hash=hash_password(request.password), nickname=request.nickname, role=request.role, status=1 ) db.add(new_user) db.commit() db.refresh(new_user) return {"success": True, "id": new_user.id} @router.delete("/users/{user_id}") async def delete_user( user_id: int, user: User = Depends(require_admin), db: Session = Depends(get_db) ): """删除用户(仅管理员)""" if user_id == user.id: raise HTTPException(status_code=400, detail="不能删除自己") target = db.query(User).filter(User.id == user_id).first() if not target: raise HTTPException(status_code=404, detail="用户不存在") db.delete(target) db.commit() return {"success": True} # ============ Token 验证 API(供外部应用调用) ============ class VerifyTokenRequest(BaseModel): """Token 验证请求""" token: str app_code: Optional[str] = None # 可选,用于验证 token 是否属于特定应用 class WechatConfig(BaseModel): """企微配置""" corp_id: Optional[str] = None agent_id: Optional[str] = None secret: Optional[str] = None class VerifyTokenResponse(BaseModel): """Token 验证响应""" valid: bool tenant_id: Optional[str] = None app_code: Optional[str] = None wechat_config: Optional[WechatConfig] = None error: Optional[str] = None @router.post("/verify", response_model=VerifyTokenResponse) async def verify_token( request: VerifyTokenRequest, db: Session = Depends(get_db) ): """ 验证 Token 有效性(供外部应用调用,无需登录) 外部应用收到用户请求后,可调用此接口验证 token: 1. 验证 token 是否存在且有效 2. 如传入 app_code,验证 token 是否属于该应用 3. 返回租户信息和企微配置 Args: token: 访问令牌 app_code: 应用代码(可选,用于验证 token 是否属于特定应用) Returns: valid: 是否有效 tenant_id: 租户ID app_code: 应用代码 wechat_config: 企微配置(如有) """ if not request.token: return VerifyTokenResponse(valid=False, error="Token 不能为空") # 根据 token 查询租户应用配置 query = db.query(TenantApp).filter( TenantApp.access_token == request.token, TenantApp.status == 1 ) # 如果指定了 app_code,验证 token 是否属于该应用 if request.app_code: query = query.filter(TenantApp.app_code == request.app_code) tenant_app = query.first() if not tenant_app: return VerifyTokenResponse(valid=False, error="Token 无效或已过期") # 获取关联的企微配置 wechat_config = None if tenant_app.wechat_app_id: wechat_app = db.query(TenantWechatApp).filter( TenantWechatApp.id == tenant_app.wechat_app_id, TenantWechatApp.status == 1 ).first() if wechat_app: # 解密 secret secret = None if wechat_app.secret_encrypted: try: secret = decrypt_config(wechat_app.secret_encrypted) except: pass wechat_config = WechatConfig( corp_id=wechat_app.corp_id, agent_id=wechat_app.agent_id, secret=secret ) return VerifyTokenResponse( valid=True, tenant_id=tenant_app.tenant_id, app_code=tenant_app.app_code, wechat_config=wechat_config ) @router.get("/verify") async def verify_token_get( token: str, app_code: Optional[str] = None, db: Session = Depends(get_db) ): """ 验证 Token(GET 方式,便于简单测试) """ return await verify_token( VerifyTokenRequest(token=token, app_code=app_code), db )