1e1e5957e8
- 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 记录
360 lines
12 KiB
Python
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": "用户已删除"}
|