Files
H3ConuMS-v2/backend/app/api/v1/auth.py
T
v6ole 4488e0ef42 fix: 路由顺序导致 422 + 彻底消除 Header(...) 隐患
1. devices.py: /tags 和 /export/csv 移到 /{device_id}(int) 之前,
   避免 FastAPI 将 'tags'/'export' 当作 device_id 解析失败返回 422
2. audit.py: /logs/export/csv 移到 /logs/{log_id}(int) 之前,同上
3. auth.py: Header(...) → Header(None),统一为手动 401 返回,
   消除全局最后一个 Header(...) 导致的 422 隐患

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-12 11:49:53 +08:00

123 lines
4.3 KiB
Python

"""认证 API"""
import base64
import json
from fastapi import APIRouter, Depends, HTTPException, Header
from pydantic import BaseModel
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.core.casdoor import casdoor_sdk
from app.core.security import create_access_token, verify_token
from app.core.config import settings
from app.models.user import User
from app.schemas.auth import Token, UserInfo
from datetime import datetime
router = APIRouter(prefix="/api/auth", tags=["认证"])
def decode_jwt_payload(token: str) -> dict:
"""直接解码 JWT payload,不验签(Casdoor 已完成认证)"""
payload_b64 = token.split(".")[1]
rem = len(payload_b64) % 4
if rem:
payload_b64 += "=" * (4 - rem)
return json.loads(base64.urlsafe_b64decode(payload_b64))
@router.get("/login")
def login():
"""获取 Casdoor 登录 URL"""
return {"url": casdoor_sdk.get_auth_link(settings.CASDOOR_REDIRECT_URL)}
class CallbackRequest(BaseModel):
code: str
state: str
@router.post("/callback", response_model=Token)
def callback(body: CallbackRequest, db: Session = Depends(get_db)):
"""Casdoor 登录回调"""
try:
token_response = casdoor_sdk.get_oauth_token(code=body.code)
if isinstance(token_response, dict) and "error" in token_response:
raise HTTPException(status_code=400, detail=token_response.get("error_description", token_response["error"]))
access_token = token_response.get("access_token") if isinstance(token_response, dict) else token_response
if not access_token:
raise HTTPException(status_code=400, detail="Casdoor 未返回 access_token")
casdoor_user = decode_jwt_payload(access_token)
user = db.query(User).filter(User.casdoor_id == casdoor_user["sub"]).first()
if not user:
user = User(
casdoor_id=casdoor_user["sub"],
username=casdoor_user.get("preferred_username") or casdoor_user.get("name", ""),
display_name=casdoor_user.get("displayName") or casdoor_user.get("name", ""),
email=casdoor_user.get("email"),
role="user"
)
db.add(user)
else:
# 每次登录同步 Casdoor 信息(姓名、邮箱等可能更新)
user.display_name = casdoor_user.get("displayName") or casdoor_user.get("name", user.display_name or "")
user.email = casdoor_user.get("email", user.email)
user.last_login = datetime.utcnow()
db.commit()
jwt_token = create_access_token({
"sub": str(user.id),
"username": user.username,
"role": user.role or "user",
})
return {"access_token": jwt_token}
except HTTPException:
raise
except Exception as e:
import traceback
import logging
logging.getLogger(__name__).error("callback error: %s\n%s", e, traceback.format_exc())
raise HTTPException(status_code=500, detail=str(e))
@router.get("/permissions")
def get_my_permissions(
authorization: str = Header(None, alias="Authorization"),
db: Session = Depends(get_db)
):
"""获取当前用户的权限码列表"""
from app.middleware.permission_middleware import get_role_permissions
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="未授权")
token = authorization[7:]
payload = verify_token(token)
if not payload:
raise HTTPException(status_code=401, detail="无效的令牌")
role = payload.get('role', 'user')
perms = get_role_permissions(role, db)
return {"role": role, "permissions": perms}
@router.get("/profile")
def get_profile(
authorization: str = Header(None, alias="Authorization"),
db: Session = Depends(get_db)
):
"""获取当前用户信息"""
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="未授权")
token = authorization[7:]
payload = verify_token(token)
if not payload:
raise HTTPException(status_code=401, detail="无效的令牌")
user = db.query(User).filter(User.id == int(payload["sub"])).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
return user