feat(auth): 统一鉴权收口,账号单一角色+权限码,去多身份

- dependencies._resolve_identity 收敛为单角色(users.role),不再读身份分支
- auth.login/select_identity 单角色发token、identities 恒空,Pine=operator(运营方)
- repositories.to_profile 按 role 计算 account_type(修复身份标签显示"服务方")
- seed 重构 PERMISSIONS/ROLE_PERMISSIONS:operator 平台全 menu、carrier 园区 menu:park_*,删多端口冗余
- account_types 归一 operator/carrier/opc_member/service

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Pine
2026-08-26 19:02:54 +08:00
parent bc6265eb67
commit 745ba678bd
5 changed files with 63 additions and 39 deletions
+4 -19
View File
@@ -27,25 +27,10 @@ def get_db(request: Request) -> Database:
return request.app.state.db
async def _resolve_identity(user: dict, identity_id: str | None, db: Database) -> dict:
"""按令牌携带的 identity_id 装配当前身份;缺失则回退到 users.role 单角色。
身份即权限来源:从 user_identities 取 role/sub_role/org/region
并据此计算 permissions 与数据范围(全异步,角色变更即时生效)。
"""
if identity_id:
ident = await db.identities.get_for_user(identity_id, user["id"])
if ident and ident.get("status") == "active":
user["identity_id"] = ident["id"]
user["port"] = ident["port"]
user["role"] = ident["role"]
user["sub_role"] = ident.get("sub_role")
user["org_id"] = ident.get("org_id")
user["region_id"] = ident.get("region_id")
user["identity_name"] = ident.get("name", "")
async def _resolve_identity(user: dict, db: Database) -> dict:
"""单账号角色(多身份已收敛):role 由 users.role 提供,仅计算权限与数据范围。"""
user["permissions"] = await db.roles.permissions_for(
user.get("role", "opc_member"), user.get("sub_role"),
user.get("role", "opc_member"), None,
)
user["scope_region_ids"] = await db.regions.visible_region_ids(user.get("region_id"))
user["scope_level"] = await db.regions.level(user.get("region_id"))
@@ -77,7 +62,7 @@ async def get_current_user(
if user is None or user.get("status") != "active":
raise HTTPException(status_code=401, detail="Invalid or expired token")
return await _resolve_identity(user, payload.get("identity_id"), db)
return await _resolve_identity(user, db)
async def optional_current_user(
+10 -15
View File
@@ -219,19 +219,19 @@ async def login(req: LoginRequest, db: Database = Depends(get_db)):
if user.get("status") != "active":
raise HTTPException(status_code=403, detail="Account is disabled")
identities = await db.identities.list_for_user(user["id"], active_only=True)
identity = identities[0] if len(identities) == 1 else None
token_record = await _issue_token(db, user, req.expires_in, identity=identity)
profile = await _profile_for(user, identity, db)
# 账号单一角色(去多身份):不再按 user_identities 选身份,按 users.role 解析账号类型。
identity = None
token_record = await _issue_token(db, user, req.expires_in, identity=None)
profile = await _profile_for(user, None, db)
await db.audit.add(
action="login", resource="auth", resource_id=user["id"],
detail=f"login {user['username']} ({len(identities)} identities)",
detail=f"login {user['username']} (account_type={profile.get('account_type', '')})",
user_id=user["id"],
)
return LoginResponse(
token=token_record["token"],
identities=_identity_summaries(identities),
identities=[],
**profile,
)
@@ -242,17 +242,12 @@ async def select_identity(
user: dict = Depends(get_current_user),
db: Database = Depends(get_db),
):
"""把当前令牌切换到指定端口身份(身份须属于当前账号且为启用状态)"""
ident = await db.identities.get_for_user(req.identity_id, user["id"])
if ident is None or ident.get("status") != "active":
raise HTTPException(status_code=404, detail="Identity not found or disabled")
token_record = await _issue_token(db, user, None, identity=ident)
profile = await _profile_for(user, ident, db)
identities = await db.identities.list_for_user(user["id"], active_only=True)
"""切换身份(身份已收敛)——恒按账号单一角色(users.role)签发,忽略 identity_id"""
token_record = await _issue_token(db, user, None, identity=None)
profile = await _profile_for(user, None, db)
return LoginResponse(
token=token_record["token"],
identities=_identity_summaries(identities),
identities=[],
**profile,
)