fix(auth): 同一账号不同登录方式权限一致 —— 登录身份由账号真实角色决定
- 新增 _login_response_for_user: 保证 C端有opc_member身份, 但令牌角色取账号真实身份(多身份→中性token, role=users.role) - phone/wx/mp/扫码/绑定 登录统一走它, 不再强制 opc_member → 手机号绑定的operator账号手机号登录也是operator - 修「admin登录无权限, 刷新才有」根因(登录角色错配)
This commit is contained in:
+28
-13
@@ -464,6 +464,22 @@ async def _login_response(
|
||||
)
|
||||
|
||||
|
||||
async def _login_response_for_user(db: Database, user: dict) -> LoginResponse:
|
||||
"""统一登录响应:令牌角色由**账号真实身份**决定,而非强制 opc_member。
|
||||
|
||||
保证同一账号无论用 手机号/微信/小程序 哪个方式登录,拿到的角色与权限都一致
|
||||
(= 用账号密码登录的结果)。规则:
|
||||
- 先 `_ensure_opc_identity` 确保 C 端也有 opc_member 身份(报名/政策等可用);
|
||||
- 用 `list_for_user` 的真实身份集合决定令牌角色:
|
||||
多身份(如 operator+opc_member)→ 中性令牌,role 取 users.role(=operator);
|
||||
仅 opc_member → opc 令牌。
|
||||
"""
|
||||
await _ensure_opc_identity(db, user["id"])
|
||||
identities = await db.identities.list_for_user(user["id"], active_only=True)
|
||||
token_identity = identities[0] if len(identities) == 1 else None
|
||||
return await _login_response(db, user, token_identity, identities)
|
||||
|
||||
|
||||
@router.post("/send-code", response_model=SendCodeResponse, summary="发送短信验证码")
|
||||
async def send_code(req: SendCodeRequest, db: Database = Depends(get_db)):
|
||||
"""为手机号发送登录验证码。stub provider 仅在服务日志打印。"""
|
||||
@@ -503,12 +519,12 @@ async def phone_login(req: PhoneLoginRequest, request: Request, db: Database = D
|
||||
if user.get("status") != "active":
|
||||
raise HTTPException(status_code=403, detail="账号已禁用")
|
||||
|
||||
identity = await _ensure_opc_identity(db, user["id"])
|
||||
await _ensure_opc_identity(db, user["id"])
|
||||
await db.audit.add(
|
||||
action="login", resource="auth", resource_id=user["id"],
|
||||
detail=f"phone-login {req.phone}", user_id=user["id"],
|
||||
)
|
||||
return await _login_response(db, user, identity)
|
||||
return await _login_response_for_user(db, user)
|
||||
|
||||
|
||||
@router.post("/wx-login", response_model=LoginResponse, summary="微信扫码登录(预留)")
|
||||
@@ -543,12 +559,12 @@ async def wx_login(req: WxLoginRequest, request: Request, db: Database = Depends
|
||||
if user.get("status") != "active":
|
||||
raise HTTPException(status_code=403, detail="账号已禁用")
|
||||
|
||||
identity = await _ensure_opc_identity(db, user["id"])
|
||||
await _ensure_opc_identity(db, user["id"])
|
||||
await db.audit.add(
|
||||
action="login", resource="auth", resource_id=user["id"],
|
||||
detail=f"wx-login {openid[:16]}", user_id=user["id"],
|
||||
)
|
||||
return await _login_response(db, user, identity)
|
||||
return await _login_response_for_user(db, user)
|
||||
|
||||
|
||||
@router.post("/wx-phone", summary="微信补绑手机号")
|
||||
@@ -569,7 +585,7 @@ async def wx_phone(req: WxPhoneRequest, db: Database = Depends(get_db)):
|
||||
action="bind-phone", resource="auth", resource_id=user["id"],
|
||||
detail=f"wx bind phone {req.phone}", user_id=user["id"],
|
||||
)
|
||||
return await _login_response(db, user, await _ensure_opc_identity(db, user["id"]))
|
||||
return await _login_response_for_user(db, user)
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
@@ -616,13 +632,13 @@ async def bind_phone(
|
||||
except sms.SmsError as exc:
|
||||
raise HTTPException(status_code=401, detail=str(exc))
|
||||
merged = await _bind_phone_merge(db, user, req.phone)
|
||||
identity = await _ensure_opc_identity(db, merged["id"])
|
||||
await _ensure_opc_identity(db, merged["id"])
|
||||
await db.audit.add(
|
||||
action="bind-phone", resource="auth", resource_id=merged["id"],
|
||||
detail=f"bind phone {req.phone} (merged)" if merged["id"] != user["id"] else f"bind phone {req.phone}",
|
||||
user_id=merged["id"],
|
||||
)
|
||||
return await _login_response(db, merged, identity)
|
||||
return await _login_response_for_user(db, merged)
|
||||
|
||||
|
||||
@router.post("/unbind", summary="解绑登录方式(保留至少一种)")
|
||||
@@ -707,8 +723,8 @@ async def wx_callback_get(
|
||||
except wechat.WechatError as exc:
|
||||
raise HTTPException(status_code=401, detail=str(exc))
|
||||
user = await _find_or_create_wx_user(openid, request, db)
|
||||
identity = await _ensure_opc_identity(db, user["id"])
|
||||
resp = await _login_response(db, user, identity)
|
||||
await _ensure_opc_identity(db, user["id"])
|
||||
resp = await _login_response_for_user(db, user)
|
||||
wx_qr_store.complete(state or openid, resp.token, resp.model_dump())
|
||||
return _wx_success_html()
|
||||
|
||||
@@ -728,8 +744,7 @@ async def wx_callback_post(
|
||||
except wechat.WechatError as exc:
|
||||
raise HTTPException(status_code=401, detail=str(exc))
|
||||
user = await _find_or_create_wx_user(openid, request, db)
|
||||
identity = await _ensure_opc_identity(db, user["id"])
|
||||
return await _login_response(db, user, identity)
|
||||
return await _login_response_for_user(db, user)
|
||||
|
||||
|
||||
@router.get("/wx-qr/poll", summary="轮询扫码登录状态")
|
||||
@@ -847,8 +862,8 @@ async def mp_qr_confirm(request: Request, req: MpQrConfirmRequest, db: Database
|
||||
)
|
||||
if user.get("status") != "active":
|
||||
raise HTTPException(status_code=403, detail="账号已禁用")
|
||||
identity = await _ensure_opc_identity(db, user["id"])
|
||||
resp = await _login_response(db, user, identity)
|
||||
await _ensure_opc_identity(db, user["id"])
|
||||
resp = await _login_response_for_user(db, user)
|
||||
wx_qr_store.complete(req.scene, resp.token, resp.model_dump())
|
||||
return {"ok": True, "username": user["username"]}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user