Skip to content

Commit 66d8f68

Browse files
authored
Merge pull request #120 from redzrush101/fix/iflow-cookie-auth-none-data
fix(iflow): add signed headers for chat requests
2 parents 570bf5b + 2265aa5 commit 66d8f68

2 files changed

Lines changed: 51 additions & 9 deletions

File tree

src/rotator_library/providers/iflow_auth_base.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,7 @@ async def _fetch_user_info(self, access_token: str) -> Dict[str, Any]:
622622
if not result.get("success"):
623623
raise ValueError("iFlow user info request not successful")
624624

625-
data = result.get("data", {})
625+
data = result.get("data") or {}
626626
api_key = data.get("apiKey", "").strip()
627627
if not api_key:
628628
raise ValueError("Missing API key in user info response")
@@ -676,7 +676,7 @@ async def _fetch_api_key_info_with_cookie(self, cookie: str) -> Dict[str, Any]:
676676
error_msg = result.get("message", "Unknown error")
677677
raise ValueError(f"Cookie authentication failed: {error_msg}")
678678

679-
data = result.get("data", {})
679+
data = result.get("data") or {}
680680

681681
# Handle case where apiKey is masked - use apiKeyMask if apiKey is empty
682682
if not data.get("apiKey") and data.get("apiKeyMask"):
@@ -732,7 +732,7 @@ async def _refresh_api_key_with_cookie(
732732
error_msg = result.get("message", "Unknown error")
733733
raise ValueError(f"Cookie API key refresh failed: {error_msg}")
734734

735-
return result.get("data", {})
735+
return result.get("data") or {}
736736

737737
async def authenticate_with_cookie(self, cookie: str) -> Dict[str, Any]:
738738
"""

src/rotator_library/providers/iflow_provider.py

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# src/rotator_library/providers/iflow_provider.py
55

66
import copy
7+
import hmac
78
import hashlib
89
import json
910
import time
@@ -69,6 +70,11 @@
6970
"response_format",
7071
}
7172

73+
IFLOW_USER_AGENT = "iFlow-Cli"
74+
IFLOW_HEADER_SESSION_ID = "session-id"
75+
IFLOW_HEADER_TIMESTAMP = "x-iflow-timestamp"
76+
IFLOW_HEADER_SIGNATURE = "x-iflow-signature"
77+
7278
# =============================================================================
7379
# THINKING MODE CONFIGURATION
7480
# =============================================================================
@@ -512,6 +518,39 @@ def _build_request_payload(
512518

513519
return payload
514520

521+
def _create_iflow_signature(
522+
self, user_agent: str, session_id: str, timestamp_ms: int, api_key: str
523+
) -> str:
524+
"""Generate iFlow HMAC-SHA256 signature: userAgent:sessionId:timestamp."""
525+
if not api_key:
526+
return ""
527+
528+
payload = f"{user_agent}:{session_id}:{timestamp_ms}"
529+
return hmac.new(
530+
api_key.encode("utf-8"), payload.encode("utf-8"), hashlib.sha256
531+
).hexdigest()
532+
533+
def _build_iflow_headers(self, api_key: str, stream: bool) -> Dict[str, str]:
534+
"""Build iFlow request headers, including signed auth headers."""
535+
session_id = f"session-{uuid.uuid4()}"
536+
timestamp_ms = int(time.time() * 1000)
537+
signature = self._create_iflow_signature(
538+
IFLOW_USER_AGENT, session_id, timestamp_ms, api_key
539+
)
540+
541+
headers = {
542+
"Authorization": f"Bearer {api_key}",
543+
"Content-Type": "application/json",
544+
"User-Agent": IFLOW_USER_AGENT,
545+
IFLOW_HEADER_SESSION_ID: session_id,
546+
IFLOW_HEADER_TIMESTAMP: str(timestamp_ms),
547+
"Accept": "text/event-stream" if stream else "application/json",
548+
}
549+
if signature:
550+
headers[IFLOW_HEADER_SIGNATURE] = signature
551+
552+
return headers
553+
515554
def _extract_finish_reason_from_chunk(self, chunk: Dict[str, Any]) -> Optional[str]:
516555
"""
517556
Extract finish_reason from a raw iFlow chunk by searching all possible locations.
@@ -922,12 +961,10 @@ async def make_request():
922961
model_name, kwargs, **kwargs_with_stripped_model
923962
)
924963

925-
headers = {
926-
"Authorization": f"Bearer {api_key}", # Uses api_key from user info
927-
"Content-Type": "application/json",
928-
"Accept": "text/event-stream",
929-
"User-Agent": "iFlow-Cli",
930-
}
964+
headers = self._build_iflow_headers(
965+
api_key=api_key,
966+
stream=bool(payload.get("stream")),
967+
)
931968

932969
url = f"{api_base.rstrip('/')}/chat/completions"
933970

@@ -983,6 +1020,11 @@ async def stream_handler(response_stream, attempt=1):
9831020

9841021
# Handle other errors
9851022
else:
1023+
if not error_text:
1024+
content_type = response.headers.get("content-type", "")
1025+
error_text = (
1026+
f"(empty response body, content-type={content_type})"
1027+
)
9861028
error_msg = (
9871029
f"iFlow HTTP {response.status_code} error: {error_text}"
9881030
)

0 commit comments

Comments
 (0)