Passkey bug fix in gunicorn
This commit is contained in:
+23
-2
@@ -544,7 +544,7 @@ def api_webauthn_register_complete():
|
||||
uid = _cur()
|
||||
|
||||
try:
|
||||
result = complete_registration(request, credential)
|
||||
result = complete_registration(request, credential, user_id=uid)
|
||||
if result.get("user_id") != uid:
|
||||
return jsonify({"error": "注册上下文不匹配,请重试"}), 400
|
||||
update_auth_config(
|
||||
@@ -569,6 +569,27 @@ def api_webauthn_register_complete():
|
||||
return jsonify({"error": f"Passkey 注册失败: {e}"}), 500
|
||||
|
||||
|
||||
@api.route("/api/auth/passkey", methods=["DELETE"])
|
||||
@login_required
|
||||
def api_remove_passkey():
|
||||
"""Remove the current user's Passkey (re-auth required).
|
||||
|
||||
If this leaves the account with no MFA method at all, the account is
|
||||
flagged must_configure_mfa so the next login forces MFA setup again
|
||||
(same guard as a fresh temp account).
|
||||
"""
|
||||
if not require_reauth():
|
||||
return jsonify({"error": "需要重新验证身份", "reauth_required": True}), 403
|
||||
uid = _cur()
|
||||
cfg = get_auth_config(user_id=uid)
|
||||
if not cfg.get("passkey_credential_id"):
|
||||
return jsonify({"error": "未配置 Passkey"}), 400
|
||||
update_auth_config(uid, passkey_credential_id="", passkey_public_key="")
|
||||
if not has_mfa_configured(user_id=uid):
|
||||
update_auth_config(uid, must_configure_mfa=1)
|
||||
return jsonify({"ok": True})
|
||||
|
||||
|
||||
# ─── TOTP Setup ──────────────────────────────
|
||||
|
||||
@api.route("/api/auth/totp/setup", methods=["POST"])
|
||||
@@ -974,7 +995,7 @@ def admin_provision_passkey_complete(user_id):
|
||||
return jsonify({"error": "用户不存在"}), 404
|
||||
|
||||
try:
|
||||
result = complete_registration(request, credential)
|
||||
result = complete_registration(request, credential, user_id=user_id)
|
||||
if result.get("user_id") != user_id:
|
||||
return jsonify({"error": "注册上下文不匹配,请重试"}), 400
|
||||
update_auth_config(
|
||||
|
||||
+23
-12
@@ -153,32 +153,43 @@ def begin_registration(request, user_id: int, display_name: str = "",
|
||||
return _serialize_options(options)
|
||||
|
||||
|
||||
def complete_registration(request, credential: dict) -> dict:
|
||||
def complete_registration(request, credential: dict, user_id: int = None) -> dict:
|
||||
"""Complete WebAuthn registration.
|
||||
|
||||
Returns {'ok': True, 'user_id': int, 'credential_id': str, 'public_key': str}
|
||||
or raises an exception on failure.
|
||||
|
||||
user_id: explicit target user. Always pass it from the route when known —
|
||||
admin provisioning runs inside the ADMIN's session (its uid differs from
|
||||
the target user, so deriving the challenge key from the session would never
|
||||
match). When omitted, it is derived from credential.user_name ("user_5" → 5),
|
||||
then from the authenticated request context.
|
||||
"""
|
||||
rp_id = _get_rp_id(request)
|
||||
origin = _get_origin(request)
|
||||
|
||||
user_name = credential.get("user_name", "")
|
||||
# Derive the target user id from the registration challenge key.
|
||||
expected_challenge = pop_webauthn_challenge(f"reg_{user_name}", db_path=_challenge_db())
|
||||
if not expected_challenge:
|
||||
# Self-registration fallback: the browser may not echo the user handle,
|
||||
# so resolve the target from the authenticated request context.
|
||||
uid = user_id
|
||||
if uid is None:
|
||||
uname = credential.get("user_name", "")
|
||||
if isinstance(uname, str) and uname.startswith("user_"):
|
||||
try:
|
||||
uid = int(uname.split("_")[-1])
|
||||
except (ValueError, IndexError):
|
||||
uid = None
|
||||
if uid is None:
|
||||
try:
|
||||
from backend.auth import current_user_id
|
||||
uid = current_user_id()
|
||||
if uid is not None:
|
||||
expected_challenge = pop_webauthn_challenge(f"reg_{uid}", db_path=_challenge_db())
|
||||
user_name = f"user_{uid}"
|
||||
except Exception:
|
||||
pass
|
||||
uid = None
|
||||
if uid is None:
|
||||
raise ValueError("No registration challenge found. Please restart registration.")
|
||||
|
||||
# Challenge key must match begin_registration's f"reg_{user_id}" exactly.
|
||||
expected_challenge = pop_webauthn_challenge(f"reg_{uid}", db_path=_challenge_db())
|
||||
if not expected_challenge:
|
||||
raise ValueError("No registration challenge found. Please restart registration.")
|
||||
user_id = int(user_name.split("_")[-1])
|
||||
user_id = uid
|
||||
|
||||
try:
|
||||
# Library expects rawId field (browser-native camelCase, base64url string)
|
||||
|
||||
Reference in New Issue
Block a user