Fix auto-login OAuth context not being established
Root cause: X-Kite-Version header on web login endpoints caused Zerodha to return plain profile response instead of OAuth redirect_url. Changes: - Remove X-Kite-Version from session headers (only valid for Kite Connect API) - Use allow_redirects=False on connect/login GET to preserve OAuth cookie - Add Referer header to login/twofa POSTs - Check data.redirect_url in twofa JSON body (modern Zerodha SPA behavior) - Keep Location header fallback for legacy behavior Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a1b19b7431
commit
4922ea69e8
@ -176,22 +176,42 @@ def _perform_zerodha_login(
|
||||
) -> dict:
|
||||
"""Automates Zerodha login and returns session data with access_token."""
|
||||
session = requests.Session()
|
||||
# Use a real browser UA; do NOT set X-Kite-Version here — that header is
|
||||
# for the Kite Connect REST API, not the web login endpoints, and confuses
|
||||
# Zerodha's routing so it returns a plain profile response instead of an
|
||||
# OAuth redirect_url.
|
||||
session.headers.update({
|
||||
"X-Kite-Version": "3",
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
||||
"User-Agent": (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/124.0.0.0 Safari/537.36"
|
||||
),
|
||||
})
|
||||
|
||||
# Step 1: Initialize OAuth session with api_key so Zerodha knows
|
||||
# which app is logging in and returns request_token after TOTP.
|
||||
session.get(
|
||||
f"https://kite.zerodha.com/connect/login?api_key={api_key}&v=3",
|
||||
# Step 1: Initialize OAuth session.
|
||||
# Use allow_redirects=False so we capture the Set-Cookie from the first
|
||||
# response before any redirect overwrites/clears the OAuth context cookie.
|
||||
connect_resp = session.get(
|
||||
f"https://kite.zerodha.com/connect/login?v=3&api_key={api_key}",
|
||||
timeout=15,
|
||||
allow_redirects=False,
|
||||
)
|
||||
print(
|
||||
f"[AUTO-LOGIN-DEBUG] connect status={connect_resp.status_code} "
|
||||
f"location={connect_resp.headers.get('Location', 'NONE')} "
|
||||
f"cookies={list(session.cookies.keys())}",
|
||||
flush=True,
|
||||
)
|
||||
# Follow the redirect to the login page so the full OAuth init completes
|
||||
redirect_to = connect_resp.headers.get("Location", "")
|
||||
if connect_resp.status_code in (301, 302, 303, 307, 308) and redirect_to:
|
||||
session.get(redirect_to, timeout=15, allow_redirects=True)
|
||||
|
||||
# Step 2: Username + password
|
||||
login_resp = session.post(
|
||||
KITE_LOGIN_ENDPOINT,
|
||||
data={"user_id": zerodha_login_id, "password": password},
|
||||
headers={"Referer": "https://kite.zerodha.com/login"},
|
||||
timeout=15,
|
||||
)
|
||||
try:
|
||||
@ -204,7 +224,7 @@ def _perform_zerodha_login(
|
||||
|
||||
request_id = login_data["data"]["request_id"]
|
||||
|
||||
# Step 3: TOTP — don't follow redirect automatically
|
||||
# Step 3: TOTP
|
||||
try:
|
||||
import pyotp
|
||||
except ImportError:
|
||||
@ -218,30 +238,44 @@ def _perform_zerodha_login(
|
||||
"twofa_value": totp_value,
|
||||
"twofa_type": "totp",
|
||||
},
|
||||
headers={"Referer": "https://kite.zerodha.com/login"},
|
||||
timeout=15,
|
||||
allow_redirects=False,
|
||||
)
|
||||
print(
|
||||
f"[AUTO-LOGIN-DEBUG] twofa status={twofa_resp.status_code} "
|
||||
f"location={twofa_resp.headers.get('Location', 'NONE')} "
|
||||
f"body={twofa_resp.text[:300]}",
|
||||
f"body={twofa_resp.text[:400]}",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
# Step 4: Follow redirects manually to intercept request_token
|
||||
# Step 4: Extract request_token.
|
||||
# Modern Zerodha (SPA): returns 200 JSON with data.redirect_url containing request_token.
|
||||
# Older behavior: 302 Location header redirect.
|
||||
request_token = None
|
||||
location = twofa_resp.headers.get("Location", "")
|
||||
|
||||
for _ in range(10):
|
||||
if "request_token" in location:
|
||||
parsed = urlparse(location)
|
||||
try:
|
||||
twofa_json = twofa_resp.json()
|
||||
redirect_url_body = twofa_json.get("data", {}).get("redirect_url", "")
|
||||
if redirect_url_body and "request_token" in redirect_url_body:
|
||||
parsed = urlparse(redirect_url_body)
|
||||
params = parse_qs(parsed.query)
|
||||
request_token = params.get("request_token", [None])[0]
|
||||
break
|
||||
if not location or twofa_resp.status_code not in (301, 302, 303, 307, 308):
|
||||
break
|
||||
twofa_resp = session.get(location, allow_redirects=False, timeout=15)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not request_token:
|
||||
location = twofa_resp.headers.get("Location", "")
|
||||
for _ in range(10):
|
||||
if "request_token" in location:
|
||||
parsed = urlparse(location)
|
||||
params = parse_qs(parsed.query)
|
||||
request_token = params.get("request_token", [None])[0]
|
||||
break
|
||||
if not location or twofa_resp.status_code not in (301, 302, 303, 307, 308):
|
||||
break
|
||||
twofa_resp = session.get(location, allow_redirects=False, timeout=15)
|
||||
location = twofa_resp.headers.get("Location", "")
|
||||
|
||||
if not request_token:
|
||||
raise AutoLoginError(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user