from fastapi import APIRouter, Depends, HTTPException, Request, status from sqlalchemy.orm import Session from datetime import datetime, timedelta import jwt import bcrypt import os from dotenv import load_dotenv from ..database import get_db from ..models import User from ..schemas import LoginRequest, TokenResponse, UserResponse from ..core.audit_logger import audit_log # 加载环境变量 load_dotenv() router = APIRouter(prefix="/api/auth", tags=["auth"]) # JWT 配置 SECRET_KEY = os.getenv("SECRET_KEY", "dev-secret-key-change-this") ALGORITHM = os.getenv("ALGORITHM", "HS256") ACCESS_TOKEN_EXPIRE_DAYS = int(os.getenv("ACCESS_TOKEN_EXPIRE_DAYS", "7")) # 默认管理员配置 DEFAULT_ADMIN_USERNAME = os.getenv("DEFAULT_ADMIN_USERNAME", "admin") DEFAULT_ADMIN_PASSWORD = os.getenv("DEFAULT_ADMIN_PASSWORD", "admin123") def create_token(user: User) -> str: """生成 JWT token""" expire = datetime.utcnow() + timedelta(days=ACCESS_TOKEN_EXPIRE_DAYS) payload = { "sub": str(user.id), "username": user.username, "role": user.role, "exp": expire } return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM) def verify_token(token: str, db: Session) -> User: """验证 JWT token 并返回用户""" try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) user_id = int(payload["sub"]) user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException(status_code=401, detail="用户不存在") return user except jwt.ExpiredSignatureError: raise HTTPException(status_code=401, detail="token 已过期") except jwt.InvalidTokenError: raise HTTPException(status_code=401, detail="无效的 token") @router.post("/login", response_model=TokenResponse) def login(login_data: LoginRequest, request: Request, db: Session = Depends(get_db)): """管理员登录(支持 bcrypt 密码验证)""" client_ip = request.client.host if request.client else None user_agent = request.headers.get("user-agent", "") # 检查是否是默认管理员(bcrypt 验证) if login_data.username == DEFAULT_ADMIN_USERNAME: # 查询或创建管理员用户 user = db.query(User).filter(User.username == DEFAULT_ADMIN_USERNAME).first() if not user: # 首次创建,密码哈希存储 hashed = bcrypt.hashpw(DEFAULT_ADMIN_PASSWORD.encode('utf-8'), bcrypt.gensalt()) user = User( username=DEFAULT_ADMIN_USERNAME, password_hash=hashed.decode('utf-8'), role="admin" ) db.add(user) db.commit() db.refresh(user) else: # 验证密码 if not user.password_hash: # 旧数据可能没有密码,设置为默认密码 user.password_hash = bcrypt.hashpw(DEFAULT_ADMIN_PASSWORD.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') db.commit() else: # 验证密码是否匹配 if not bcrypt.checkpw(login_data.password.encode('utf-8'), user.password_hash.encode('utf-8')): # 记录失败日志 audit_log( action="login_failed", username=login_data.username, details={"reason": "invalid_password"}, ip_address=client_ip, user_agent=user_agent, db=db ) raise HTTPException(status_code=401, detail="用户名或密码错误") token = create_token(user) # 记录成功登录日志 audit_log( action="login", user=user, details={"role": user.role}, ip_address=client_ip, user_agent=user_agent, db=db ) return TokenResponse(token=token, role=user.role, user=UserResponse.model_validate(user)) # 从数据库查询其他用户 user = db.query(User).filter(User.username == login_data.username).first() if not user: audit_log( action="login_failed", username=login_data.username, details={"reason": "user_not_found"}, ip_address=client_ip, user_agent=user_agent, db=db ) raise HTTPException(status_code=401, detail="用户名或密码错误") # bcrypt 验证 if not user.password_hash or not bcrypt.checkpw(login_data.password.encode('utf-8'), user.password_hash.encode('utf-8')): audit_log( action="login_failed", user=user, details={"reason": "invalid_password"}, ip_address=client_ip, user_agent=user_agent, db=db ) raise HTTPException(status_code=401, detail="用户名或密码错误") token = create_token(user) audit_log( action="login", user=user, details={"role": user.role}, ip_address=client_ip, user_agent=user_agent, db=db ) return TokenResponse(token=token, role=user.role, user=UserResponse.model_validate(user)) def get_current_user(request: Request, db: Session = Depends(get_db)) -> User: """依赖项:验证用户登录""" auth_header = request.headers.get("Authorization") if not auth_header or not auth_header.startswith("Bearer "): raise HTTPException(status_code=401, detail="未提供认证令牌") token = auth_header.split(" ")[1] user = verify_token(token, db) return user def get_current_admin(current_user: User = Depends(get_current_user)) -> User: """依赖项:验证管理员权限""" if current_user.role != "admin": raise HTTPException(status_code=403, detail="需要管理员权限") return current_user @router.get("/me") def get_me( request: Request, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """获取当前登录用户信息""" return {"user": UserResponse.model_validate(current_user)}