feat(auth): POST /wechat/bind-phone/jverify(极光本机号绑号)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,7 @@ from app.schemas.auth import (
|
||||
TokenPair,
|
||||
TokenWithUser,
|
||||
UserOut,
|
||||
WechatBindPhoneJverifyRequest,
|
||||
WechatBindPhoneSmsRequest,
|
||||
WechatBindResultResponse,
|
||||
WechatLoginRequest,
|
||||
@@ -300,6 +301,35 @@ def wechat_bind_phone_sms(
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/wechat/bind-phone/jverify",
|
||||
response_model=WechatBindResultResponse,
|
||||
summary="微信登录·本机号(极光)绑定",
|
||||
)
|
||||
def wechat_bind_phone_jverify(
|
||||
req: WechatBindPhoneJverifyRequest, db: DbSession
|
||||
) -> WechatBindResultResponse:
|
||||
try:
|
||||
claims = decode_bind_ticket(req.bind_ticket)
|
||||
except TokenError as e:
|
||||
raise HTTPException(status_code=401, detail="授权已过期,请重新用微信登录") from e
|
||||
|
||||
try:
|
||||
phone = verify_and_get_phone(req.login_token)
|
||||
except JiguangError as e:
|
||||
logger.error("[JG] verify+decrypt failed: %s", e, exc_info=True)
|
||||
raise HTTPException(status_code=502, detail=f"jiguang verify failed: {e}") from e
|
||||
|
||||
return _finish_wechat_bind(
|
||||
db,
|
||||
openid=claims["openid"],
|
||||
wechat_nickname=claims["wnk"],
|
||||
wechat_avatar_url=claims["wav"],
|
||||
phone=phone,
|
||||
device_id=req.device_id,
|
||||
)
|
||||
|
||||
|
||||
# ===================== Refresh =====================
|
||||
|
||||
@router.post("/refresh", response_model=TokenPair, summary="用 refresh_token 换新 token 对")
|
||||
|
||||
@@ -143,3 +143,9 @@ class WechatBindPhoneSmsRequest(BaseModel):
|
||||
phone: str = Field(..., min_length=11, max_length=11, pattern=r"^1\d{10}$")
|
||||
code: str = Field(..., min_length=4, max_length=8)
|
||||
device_id: str = Field("", max_length=64)
|
||||
|
||||
|
||||
class WechatBindPhoneJverifyRequest(BaseModel):
|
||||
bind_ticket: str = Field(..., min_length=1)
|
||||
login_token: str = Field(..., min_length=1, description="客户端 loginAuth 拿到的 loginToken")
|
||||
device_id: str = Field("", max_length=64)
|
||||
|
||||
@@ -8,6 +8,7 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from app.api.v1 import auth
|
||||
from app.core import security
|
||||
from app.integrations import wxpay
|
||||
|
||||
@@ -138,3 +139,31 @@ def test_wechat_bind_sms_expired_ticket_returns_401(client, monkeypatch) -> None
|
||||
json={"bind_ticket": expired, "phone": "13900139009", "code": "123456", "device_id": "devD"},
|
||||
)
|
||||
assert r.status_code == 401, r.text
|
||||
|
||||
|
||||
# ===== Task 4: bind-phone/jverify =====
|
||||
|
||||
def test_wechat_bind_jverify_creates_account(client, monkeypatch) -> None:
|
||||
"""本机号(极光)绑定路径:verify_and_get_phone 拦掉,未占用 → 建号登入。"""
|
||||
monkeypatch.setattr(wxpay, "code_to_userinfo", _fake_userinfo("openid_jv_5", "极光用户", None))
|
||||
phone = "13900139005"
|
||||
# 极光 loginToken→手机号 在 auth 模块命名空间打桩(auth.py 顶部 from ...jiguang import verify_and_get_phone)
|
||||
monkeypatch.setattr(auth, "verify_and_get_phone", lambda token: phone)
|
||||
|
||||
ticket = client.post(
|
||||
"/api/v1/auth/wechat-login", json={"code": "c", "device_id": "devE"}
|
||||
).json()["bind_ticket"]
|
||||
|
||||
r = client.post(
|
||||
"/api/v1/auth/wechat/bind-phone/jverify",
|
||||
json={"bind_ticket": ticket, "login_token": "jgtoken", "device_id": "devE"},
|
||||
)
|
||||
assert r.status_code == 200, r.text
|
||||
body = r.json()
|
||||
assert body["status"] == "logged_in"
|
||||
user = body["token"]["user"]
|
||||
assert user["phone"] == phone
|
||||
assert user["register_channel"] == "wechat"
|
||||
assert user["nickname"] == "极光用户"
|
||||
# 微信 userinfo 隐私脱敏 avatar=None → 头像为空(客户端兜底默认头像)
|
||||
assert user["avatar_url"] is None
|
||||
|
||||
Reference in New Issue
Block a user