feat(auth): 停用账密登录 + 扫码会话有效期下发 + 叠加制身份模型
- /auth/login 全端口停用(403 引导手机号/小程序扫码登录) - 扫码会话接口下发 expires_in(前端二维码倒计时) - role_allowed 叠加制身份:所有账号自带 OPC 基础身份,operator/carrier 仅业务叠加,35 处 C 端端点统一放行
This commit is contained in:
+15
-5
@@ -96,7 +96,7 @@ def _client_ip(request: Request) -> str:
|
||||
xff = request.headers.get("X-Forwarded-For", "")
|
||||
if xff:
|
||||
return xff.split(",")[0].strip()
|
||||
return request.client.host if (request.client and request.client.address) else ""
|
||||
return request.client.host if request.client else ""
|
||||
|
||||
# 账号统一使用手机号作为登录账号(11 位,1 开头)
|
||||
_PHONE_RE = re.compile(r"^1\d{10}$")
|
||||
@@ -198,6 +198,11 @@ async def login(req: LoginRequest, db: Database = Depends(get_db)):
|
||||
- 多个身份:签发中性账号令牌,前端展示身份选择,经
|
||||
``/auth/select-identity`` 切换到指定身份后进入对应端口。
|
||||
"""
|
||||
# 账号密码登录已全端口停用:统一手机号验证码 / 小程序扫码登录(身份叠加制,角色仅是账号上的业务绑定)
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="账号密码登录已停用,请使用手机号验证码或小程序扫码登录",
|
||||
)
|
||||
user = await db.users.get_by_username(req.username)
|
||||
if user is None or not await db.users.verify_password(user, req.password):
|
||||
raise HTTPException(status_code=401, detail="Invalid username or password")
|
||||
@@ -313,7 +318,12 @@ async def update_profile(
|
||||
if new_password is not None and not new_password.strip():
|
||||
raise HTTPException(status_code=400, detail="Password cannot be empty")
|
||||
|
||||
from ...infrastructure.oss import to_object_path
|
||||
profile_updates = {k: payload[k] for k in _PROFILE_FIELDS if k in payload}
|
||||
# 头像等资源字段入库归一化:完整 URL → 对象路径(/oss/<key>),出口再生成 URL
|
||||
for _k in ("avatar", "company_avatar"):
|
||||
if _k in profile_updates:
|
||||
profile_updates[_k] = to_object_path(profile_updates[_k])
|
||||
# 小程序契约字段 → users 列映射(name→nickname,status→opc_status,topics→JSON文本)
|
||||
if "name" in payload:
|
||||
profile_updates["nickname"] = payload["name"]
|
||||
@@ -717,11 +727,11 @@ async def mp_qr_start(request: Request, db: Database = Depends(get_db)):
|
||||
try:
|
||||
png = await wechat.get_wxacode(scene, page="pages/scan-login/index")
|
||||
b64 = base64.b64encode(png).decode("ascii")
|
||||
return {"scene": scene, "qr_image": f"data:image/png;base64,{b64}", "mp_enabled": True}
|
||||
return {"scene": scene, "qr_image": f"data:image/png;base64,{b64}", "mp_enabled": True, "expires_in": wx_qr_store.TTL_SECONDS}
|
||||
except wechat.WechatError:
|
||||
# 无凭据/失败兜底:返回可扫描的 URL,前端按 qr_url 渲染普通二维码
|
||||
qr_url = f"https://opc.pinesound.cn/mp/login?scene={scene}"
|
||||
return {"scene": scene, "qr_url": qr_url, "mp_enabled": False}
|
||||
return {"scene": scene, "qr_url": qr_url, "mp_enabled": False, "expires_in": wx_qr_store.TTL_SECONDS}
|
||||
|
||||
|
||||
@router.get("/mp-qr/poll", summary="轮询小程序扫码登录状态")
|
||||
@@ -747,10 +757,10 @@ async def mp_qr_bind_start(request: Request, db: Database = Depends(get_db), use
|
||||
try:
|
||||
png = await wechat.get_wxacode(scene, page="pages/scan-login/index")
|
||||
b64 = base64.b64encode(png).decode("ascii")
|
||||
return {"scene": scene, "qr_image": f"data:image/png;base64,{b64}", "mp_enabled": True}
|
||||
return {"scene": scene, "qr_image": f"data:image/png;base64,{b64}", "mp_enabled": True, "expires_in": wx_qr_store.TTL_SECONDS}
|
||||
except wechat.WechatError:
|
||||
qr_url = f"https://opc.pinesound.cn/mp/bind?scene={scene}"
|
||||
return {"scene": scene, "qr_url": qr_url, "mp_enabled": False}
|
||||
return {"scene": scene, "qr_url": qr_url, "mp_enabled": False, "expires_in": wx_qr_store.TTL_SECONDS}
|
||||
|
||||
|
||||
@router.post("/mp-qr/confirm", summary="小程序内确认扫码登录/绑定")
|
||||
|
||||
Reference in New Issue
Block a user