Passkey bug fix in gunicorn

This commit is contained in:
喆OvO
2026-08-13 12:50:18 +08:00
parent 15b54f204e
commit 530b8a8269
4 changed files with 131 additions and 16 deletions
+68 -2
View File
@@ -655,8 +655,10 @@ orig_complete_auth = routes_mod.complete_authentication
routes_mod.begin_registration = lambda request, user_id, display_name="", existing_credential_id=None: {
"challenge": "AA", "user": {"id": b64url(f"user_{user_id}".encode().hex()), "name": f"user_{user_id}", "displayName": display_name},
}
routes_mod.complete_registration = lambda request, credential: {
"ok": True, "user_id": int(credential.get("user_name", "user_0").split("_")[-1]),
routes_mod.complete_registration = lambda request, credential, user_id=None: {
"ok": True,
"user_id": user_id if user_id is not None
else int(credential.get("user_name", "user_0").split("_")[-1]),
"credential_id": CRED_HEX, "public_key": "abcdef",
}
routes_mod.begin_authentication = lambda request, allow_credential_ids=None, challenge_key="login": {"challenge": "BB"}
@@ -723,6 +725,20 @@ routes_mod.complete_registration = orig_complete_reg
routes_mod.begin_authentication = orig_begin_auth
routes_mod.complete_authentication = orig_complete_auth
# 移除 Passkey(Root 自助,TOTP reauth)
rr = reauth_totp(root15, root15_secret)
r = root15.delete("/api/auth/passkey")
check("移除 Passkey 成功", r.status_code == 200, str(r.get_json()))
cfg = get_auth_config(db15, 0)
check("移除后凭据清空", cfg["passkey_credential_id"] == "" and cfg["passkey_public_key"] == "")
check("仍有 TOTP 不强制重配 MFA", cfg["must_configure_mfa"] == 0)
r = root15.delete("/api/auth/passkey")
check("未配置时重复移除 → 400", r.status_code == 400)
# 未 reauth 的会话移除 → 403
fresh15, _ = do_login(app15.test_client(), pyotp.TOTP(root15_secret).now())
r = fresh15.delete("/api/auth/passkey")
check("移除未 reauth → 403", r.status_code == 403 and r.get_json().get("reauth_required"))
print()
print("=" * 60)
print("TEST 14: Scheduler thread context (per-user timezone, no request context)")
@@ -1158,6 +1174,56 @@ check("下一个请求立即读到新时区(无陈旧缓存)", r.get_json()[
st = c22.get("/api/display/state").get_json()
check("display/state 返回新时区", st.get("timezone") == "America/New_York")
print()
print("=" * 60)
print("TEST 23: Passkey 注册 challenge key 匹配(admin 代配非 Root 用户,真实 webauthn_mgr)")
print("=" * 60)
from backend import webauthn_mgr as wm
import base64 as _b64
app23, db23 = fresh_app("t23")
root23 = root_client(app23, db23)
root23_secret = get_auth_config(db23, 0)["totp_secret"]
rr = reauth_totp(root23, root23_secret)
r = root23.post("/api/admin/users", json={"remark": "PK Target", "mode": "admin_fills"})
target_id = r.get_json()["user"]["id"]
# 真实 begin_registration(admin 代配,目标用户非 Root)
with app23.test_request_context():
from flask import request as _req
opts = wm.begin_registration(_req, user_id=target_id, display_name="PK Target")
challenge_b64 = opts["challenge"]
got = pop_webauthn_challenge(f"reg_{target_id}", db_path=db23)
check("challenge 存于 reg_<uid>(key 拼装一致)", got == challenge_b64)
# 用显式 user_id 的 complete_registration(mock 掉 verify,验证 key 匹配路径)
with app23.test_request_context():
_req2 = _req
wm.begin_registration(_req2, user_id=target_id, display_name="PK Target")
class _FakeVer:
credential_id = bytes.fromhex("aabb" * 8)
credential_public_key = bytes.fromhex("ccdd" * 8)
_orig_verify = wm.verify_registration_response
wm.verify_registration_response = lambda **kw: _FakeVer()
try:
with app23.test_request_context():
res = wm.complete_registration(_req2, {
"id": "qqq", "rawId": "qqq", "user_name": f"user_{target_id}",
}, user_id=target_id)
check("admin 代配 complete 显式 user_id 命中 challenge", res["user_id"] == target_id)
finally:
wm.verify_registration_response = _orig_verify
# 反向验证:若 key 不匹配(如用错 user_id)→ 明确报错
with app23.test_request_context():
wm.begin_registration(_req2, user_id=target_id, display_name="PK Target")
try:
with app23.test_request_context():
wm.complete_registration(_req2, {"id": "qqq", "rawId": "qqq"}, user_id=0)
check("错误 user_id 被拒绝", False, "没有抛错")
except ValueError as e:
check("错误 user_id 被拒绝(challenge 不匹配)", "No registration challenge found" in str(e))
print()
print("=" * 60)
print(f"RESULT: {PASS} passed, {FAIL} failed")