Files
ipam/backend/app/api/v1/auth.py
T
Your Name 1e1e5957e8 fix(ipam): 修复IP管理页面网段筛选不生效及下拉框过窄
- Ips.vue: el-select 添加 width: 280px,修复网段下拉框过窄
- Ips.vue: query 参数 networkId → network_id(与 FastAPI snake_case 一致)
- Scan.vue: el-select 用 network.id 代替整个 network 对象作为 value,修复切换网段不生效
- enhanced_scan_service.py: 替换失效的 scapy ARP 扫描为 arp-scan 命令;扫描结果自动创建缺失 IP 记录
2026-07-18 09:45:30 +08:00

360 lines
12 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, status, Request
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
from typing import Optional
from app.core.database import get_db
from app.core.security import get_current_user, require_permission
from app.models.auth import User, UserRole, UserStatus
from app.services.user_service import UserService, TokenService
from app.services.audit_service import AuditService, AuditAction
router = APIRouter(prefix="/auth", tags=["认证管理"])
@router.post("/login", summary="用户登录")
def login(
request: Request,
form_data: OAuth2PasswordRequestForm = Depends(),
db: Session = Depends(get_db)
):
"""用户登录,获取访问令牌"""
user = UserService.authenticate(db, form_data.username, form_data.password)
if not user:
# 记录失败尝试
AuditService.record(
db,
action=AuditAction.USER_LOGIN_FAILED,
username=form_data.username,
method="POST",
path=str(request.url.path),
ip_address=request.client.host if request.client else "",
user_agent=(request.headers.get("user-agent") or "")[:500],
status="failed",
)
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="用户名或密码错误",
headers={"WWW-Authenticate": "Bearer"},
)
# 更新登录信息
UserService.update_login_info(db, user.id, ip_address=request.client.host if request.client else None)
# 创建令牌
tokens = TokenService.create_tokens(
db, user,
ip_address=request.client.host if request.client else None,
user_agent=(request.headers.get("user-agent") or "")[:500],
)
AuditService.record(
db,
action=AuditAction.USER_LOGIN,
user=user,
method="POST",
path=str(request.url.path),
ip_address=request.client.host if request.client else "",
user_agent=(request.headers.get("user-agent") or "")[:500],
)
return {
**tokens,
"user_info": {
"id": user.id,
"username": user.username,
"email": user.email,
"real_name": user.real_name,
"role": user.role.value
}
}
@router.post("/refresh", summary="刷新访问令牌")
def refresh_token(refresh_token: str, db: Session = Depends(get_db)):
"""使用刷新令牌获取新的访问令牌"""
result = TokenService.refresh_access_token(db, refresh_token)
if not result:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无效的刷新令牌或已过期",
headers={"WWW-Authenticate": "Bearer"},
)
return result
@router.post("/logout", summary="登出")
def logout(
request: Request,
refresh_token: str,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""用户登出,撤销刷新令牌"""
TokenService.revoke_token(db, refresh_token)
AuditService.record(
db,
action=AuditAction.USER_LOGOUT,
user=current_user,
method="POST",
path=str(request.url.path),
ip_address=request.client.host if request.client else "",
user_agent=(request.headers.get("user-agent") or "")[:500],
)
return {"message": "登出成功"}
@router.get("/me", summary="获取当前用户信息")
def get_current_user_info(current_user: User = Depends(get_current_user)):
"""获取当前登录用户信息"""
return {
"id": current_user.id,
"username": current_user.username,
"email": current_user.email,
"phone": current_user.phone,
"real_name": current_user.real_name,
"role": current_user.role.value,
"status": current_user.status.value,
"last_login_at": current_user.last_login_at,
"email_notification": current_user.email_notification,
"wechat_notification": current_user.wechat_notification,
"dingtalk_notification": current_user.dingtalk_notification
}
@router.post("/change-password", summary="修改密码")
def change_password(
old_password: str,
new_password: str,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""用户修改自己的密码"""
if len(new_password) < 6:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="新密码长度不能少于6位"
)
try:
UserService.change_password(db, current_user.id, old_password, new_password)
return {"message": "密码修改成功,请重新登录"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"密码修改失败: {str(e)}"
)
# ========== 用户管理 API (需要管理员权限) ==========
@router.get("/users", summary="获取用户列表")
def get_users(
status: Optional[UserStatus] = None,
role: Optional[UserRole] = None,
skip: int = 0,
limit: int = 50,
current_user: User = Depends(require_permission("user:view")),
db: Session = Depends(get_db)
):
"""获取用户列表(需要管理员权限)"""
total, items = UserService.get_users(db, skip=skip, limit=limit, status=status, role=role)
# 返回脱敏的用户信息
user_list = []
for user in items:
user_list.append({
"id": user.id,
"username": user.username,
"email": user.email,
"phone": user.phone,
"real_name": user.real_name,
"role": user.role.value,
"status": user.status.value,
"last_login_at": user.last_login_at,
"created_at": user.created_at
})
return {"total": total, "items": user_list}
@router.get("/users/{user_id}", summary="获取用户详情")
def get_user(
user_id: int,
current_user: User = Depends(require_permission("user:view")),
db: Session = Depends(get_db)
):
"""获取用户详情(需要管理员权限)"""
user = UserService.get_user_by_id(db, user_id)
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
return {
"id": user.id,
"username": user.username,
"email": user.email,
"phone": user.phone,
"real_name": user.real_name,
"role": user.role.value,
"status": user.status.value,
"last_login_at": user.last_login_at,
"last_login_ip": user.last_login_ip,
"login_failed_count": user.login_failed_count,
"created_at": user.created_at,
"email_notification": user.email_notification,
"wechat_notification": user.wechat_notification,
"dingtalk_notification": user.dingtalk_notification
}
@router.post("/users", summary="创建用户", status_code=status.HTTP_201_CREATED)
def create_user(
username: str,
password: str,
email: Optional[str] = None,
phone: Optional[str] = None,
real_name: Optional[str] = None,
role: UserRole = UserRole.VIEWER,
current_user: User = Depends(require_permission("user:create")),
db: Session = Depends(get_db)
):
"""创建新用户(需要超级管理员权限)"""
# 只有超级管理员才能创建管理员
if role in [UserRole.SUPER_ADMIN, UserRole.ADMIN] and current_user.role != UserRole.SUPER_ADMIN:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="无权创建管理员账户"
)
if len(password) < 6:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="密码长度不能少于6位"
)
user = UserService.create_user(
db,
username=username,
password=password,
email=email,
phone=phone,
real_name=real_name,
role=role,
created_by=current_user.id
)
return {
"message": "用户创建成功",
"user": {
"id": user.id,
"username": user.username,
"role": user.role.value
}
}
@router.put("/users/{user_id}/status", summary="更新用户状态")
def update_user_status(
user_id: int,
status: UserStatus,
current_user: User = Depends(require_permission("user:edit")),
db: Session = Depends(get_db)
):
"""启用/禁用用户"""
# 不能禁用自己
if user_id == current_user.id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="不能禁用自己的账户"
)
user = UserService.update_user_status(db, user_id, status)
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
return {"message": "用户状态更新成功", "new_status": status.value}
@router.post("/users/{user_id}/unlock", summary="解锁用户")
def unlock_user(
user_id: int,
current_user: User = Depends(require_permission("user:edit")),
db: Session = Depends(get_db)
):
"""解锁被锁定的用户"""
user = UserService.unlock_user(db, user_id)
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
return {"message": "用户已解锁"}
@router.post("/users/{user_id}/reset-password", summary="重置用户密码")
def reset_user_password(
user_id: int,
new_password: str,
current_user: User = Depends(require_permission("user:edit")),
db: Session = Depends(get_db)
):
"""管理员重置用户密码"""
if len(new_password) < 6:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="密码长度不能少于6位"
)
# 只有超级管理员才能重置其他管理员的密码
target_user = UserService.get_user_by_id(db, user_id)
if not target_user:
raise HTTPException(status_code=404, detail="用户不存在")
if target_user.role in [UserRole.SUPER_ADMIN, UserRole.ADMIN] and current_user.role != UserRole.SUPER_ADMIN:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="无权重置管理员密码"
)
success = UserService.reset_password(db, user_id, new_password)
if not success:
raise HTTPException(status_code=404, detail="用户不存在")
# 撤销该用户所有现有令牌,迫使其重新登录
TokenService.revoke_all_user_tokens(db, user_id)
return {"message": "密码重置成功,用户所有会话已失效"}
@router.delete("/users/{user_id}", summary="删除用户")
def delete_user(
user_id: int,
current_user: User = Depends(require_permission("user:delete")),
db: Session = Depends(get_db)
):
"""删除用户(软删除,标记为禁用)"""
# 不能删除自己
if user_id == current_user.id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="不能删除自己的账户"
)
# 只有超级管理员才能删除管理员
target_user = UserService.get_user_by_id(db, user_id)
if target_user and target_user.role in [UserRole.SUPER_ADMIN, UserRole.ADMIN]:
if current_user.role != UserRole.SUPER_ADMIN:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="无权删除管理员账户"
)
success = UserService.delete_user(db, user_id)
if not success:
raise HTTPException(status_code=404, detail="用户不存在")
# 撤销所有令牌
TokenService.revoke_all_user_tokens(db, user_id)
return {"message": "用户已删除"}