更新
This commit is contained in:
+134
-12
@@ -25,16 +25,30 @@ from .email_verification import create_verification_token, mask_email, verify_em
|
||||
from .password_reset import create_password_reset_token, verify_password_reset_token
|
||||
from .jwt_utils import create_access_token
|
||||
from .passwords import hash_password, verify_password
|
||||
from .roles import ALL_ROLES, ROLE_LABELS, ROLE_OPERATOR, ensure_role, is_admin
|
||||
from .permissions import permission_catalog
|
||||
from .role_service import (
|
||||
count_users_with_role,
|
||||
create_role,
|
||||
delete_role,
|
||||
ensure_role_assignable,
|
||||
guard_last_admin_change,
|
||||
list_roles as list_role_records,
|
||||
update_role,
|
||||
user_permission_payload,
|
||||
)
|
||||
from .roles import ROLE_OPERATOR, is_admin
|
||||
from .schemas import (
|
||||
LoginRequest,
|
||||
MessageResponse,
|
||||
ForgotPasswordRequest,
|
||||
ForgotPasswordResponse,
|
||||
PermissionCatalogResponse,
|
||||
RegisterRequest,
|
||||
RegisterResponse,
|
||||
ResendVerificationRequest,
|
||||
ResetPasswordRequest,
|
||||
RoleCreate,
|
||||
RoleUpdate,
|
||||
RolesResponse,
|
||||
RoleInfo,
|
||||
TokenResponse,
|
||||
@@ -52,6 +66,10 @@ router = APIRouter(prefix="/api/auth", tags=["auth"])
|
||||
|
||||
async def _build_user_response(db: AsyncSession, user: User, with_count: bool = False) -> UserResponse:
|
||||
payload = UserResponse.model_validate(user)
|
||||
perm = user_permission_payload(user.role)
|
||||
payload.role_label = perm["role_label"]
|
||||
payload.is_admin = perm["is_admin"]
|
||||
payload.permissions = perm["permissions"]
|
||||
if with_count:
|
||||
breakdown = await count_user_account_breakdown(db, user.id)
|
||||
payload.account_count = breakdown["total"]
|
||||
@@ -371,13 +389,29 @@ async def get_me(user: User = Depends(get_current_user), db: AsyncSession = Depe
|
||||
|
||||
|
||||
@router.get("/roles", response_model=RolesResponse)
|
||||
async def list_roles(_: User = Depends(get_current_user)):
|
||||
async def list_auth_roles(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: User = Depends(get_current_user),
|
||||
):
|
||||
"""Lightweight role list for dropdowns (any logged-in user)."""
|
||||
records = await list_role_records(db)
|
||||
return RolesResponse(
|
||||
roles=[RoleInfo(value=r, label=ROLE_LABELS.get(r, r)) for r in ALL_ROLES]
|
||||
roles=[
|
||||
RoleInfo(
|
||||
value=item.code,
|
||||
label=item.label,
|
||||
description=item.description or None,
|
||||
is_system=item.is_system,
|
||||
is_admin=item.is_admin,
|
||||
permissions=list(item.permissions),
|
||||
)
|
||||
for item in records
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
users_router = APIRouter(prefix="/api/users", tags=["users"])
|
||||
roles_router = APIRouter(prefix="/api/roles", tags=["roles"])
|
||||
|
||||
|
||||
@users_router.get("", response_model=list[UserResponse])
|
||||
@@ -403,10 +437,7 @@ async def create_user(
|
||||
exists = await db.execute(select(User).where(User.username == body.username))
|
||||
if exists.scalar_one_or_none():
|
||||
raise HTTPException(status_code=400, detail="用户名已存在")
|
||||
try:
|
||||
role = ensure_role(body.role)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
role = await ensure_role_assignable(db, body.role)
|
||||
email = await _ensure_email_available(db, str(body.email) if body.email else None)
|
||||
if settings.email_binding_required and not is_admin(role) and not email:
|
||||
raise HTTPException(status_code=400, detail="系统已开启「登录必须绑定邮箱」,请填写邮箱")
|
||||
@@ -445,14 +476,19 @@ async def update_user(
|
||||
settings = await load_settings(db)
|
||||
if user.id == current.id and body.is_active is False:
|
||||
raise HTTPException(status_code=400, detail="不能禁用当前登录账号")
|
||||
|
||||
updates = body.model_dump(exclude_unset=True)
|
||||
if body.is_active is False:
|
||||
await guard_last_admin_change(db, user=user, deactivating=True)
|
||||
if body.role is not None:
|
||||
new_role = await ensure_role_assignable(db, body.role)
|
||||
await guard_last_admin_change(db, user=user, new_role=new_role)
|
||||
|
||||
if body.display_name is not None:
|
||||
user.display_name = body.display_name
|
||||
if body.role is not None:
|
||||
prev_role = user.role
|
||||
try:
|
||||
user.role = ensure_role(body.role)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
user.role = await ensure_role_assignable(db, body.role)
|
||||
if is_admin(user.role):
|
||||
user.max_accounts = UNLIMITED_ACCOUNTS
|
||||
await sync_user_account_quota(db, user, stop_worker=default_stop_worker)
|
||||
@@ -465,7 +501,6 @@ async def update_user(
|
||||
if body.password:
|
||||
user.password_hash = hash_password(body.password)
|
||||
|
||||
updates = body.model_dump(exclude_unset=True)
|
||||
if "max_accounts" in updates and not is_admin(user.role):
|
||||
user.max_accounts = normalize_max_accounts(updates["max_accounts"], user.role)
|
||||
await sync_user_account_quota(db, user, stop_worker=default_stop_worker)
|
||||
@@ -506,6 +541,93 @@ async def delete_user(
|
||||
user = result.scalar_one_or_none()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
await guard_last_admin_change(db, user=user, deleting=True)
|
||||
await db.delete(user)
|
||||
await db.commit()
|
||||
return {"message": "用户已删除"}
|
||||
|
||||
|
||||
@roles_router.get("", response_model=RolesResponse)
|
||||
async def admin_list_roles(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: User = Depends(require_user_manager),
|
||||
):
|
||||
records = await list_role_records(db)
|
||||
roles = []
|
||||
for item in records:
|
||||
roles.append(
|
||||
RoleInfo(
|
||||
value=item.code,
|
||||
label=item.label,
|
||||
description=item.description or None,
|
||||
is_system=item.is_system,
|
||||
is_admin=item.is_admin,
|
||||
permissions=list(item.permissions),
|
||||
user_count=await count_users_with_role(db, item.code),
|
||||
)
|
||||
)
|
||||
return RolesResponse(roles=roles)
|
||||
|
||||
|
||||
@roles_router.get("/catalog", response_model=PermissionCatalogResponse)
|
||||
async def get_permission_catalog(_: User = Depends(require_user_manager)):
|
||||
return PermissionCatalogResponse(**permission_catalog())
|
||||
|
||||
|
||||
@roles_router.post("", response_model=RoleInfo)
|
||||
async def create_custom_role(
|
||||
body: RoleCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: User = Depends(require_user_manager),
|
||||
):
|
||||
record = await create_role(
|
||||
db,
|
||||
code=body.code,
|
||||
label=body.label,
|
||||
description=body.description,
|
||||
permissions=body.permissions,
|
||||
)
|
||||
return RoleInfo(
|
||||
value=record.code,
|
||||
label=record.label,
|
||||
description=record.description or None,
|
||||
is_system=record.is_system,
|
||||
is_admin=record.is_admin,
|
||||
permissions=list(record.permissions),
|
||||
user_count=0,
|
||||
)
|
||||
|
||||
|
||||
@roles_router.put("/{code}", response_model=RoleInfo)
|
||||
async def update_custom_role(
|
||||
code: str,
|
||||
body: RoleUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: User = Depends(require_user_manager),
|
||||
):
|
||||
record = await update_role(
|
||||
db,
|
||||
code,
|
||||
label=body.label,
|
||||
description=body.description,
|
||||
permissions=body.permissions,
|
||||
)
|
||||
return RoleInfo(
|
||||
value=record.code,
|
||||
label=record.label,
|
||||
description=record.description or None,
|
||||
is_system=record.is_system,
|
||||
is_admin=record.is_admin,
|
||||
permissions=list(record.permissions),
|
||||
user_count=await count_users_with_role(db, record.code),
|
||||
)
|
||||
|
||||
|
||||
@roles_router.delete("/{code}")
|
||||
async def delete_custom_role(
|
||||
code: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: User = Depends(require_user_manager),
|
||||
):
|
||||
await delete_role(db, code)
|
||||
return {"message": "角色已删除"}
|
||||
|
||||
Reference in New Issue
Block a user