fix env auth
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
OPENLANE_HEADLESS=true
|
OPENLANE_HEADLESS=true
|
||||||
OPENLANE_LOG_LEVEL=INFO
|
OPENLANE_LOG_LEVEL=INFO
|
||||||
OPENLANE_TIMEOUT_MS=45000
|
OPENLANE_TIMEOUT_MS=45000
|
||||||
|
OPENLANE_RUNTIME_CONFIG_FILE=runtime_config.json
|
||||||
|
|
||||||
# --- OpenLane Auth & API ---
|
# --- OpenLane Auth & API ---
|
||||||
OPENLANE_USERNAME=
|
OPENLANE_USERNAME=
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ class Settings:
|
|||||||
log_level: str = _env_str("OPENLANE_LOG_LEVEL", "INFO")
|
log_level: str = _env_str("OPENLANE_LOG_LEVEL", "INFO")
|
||||||
log_file: str | None = _env_optional_str("OPENLANE_LOG_FILE")
|
log_file: str | None = _env_optional_str("OPENLANE_LOG_FILE")
|
||||||
enable_trace_id_logs: bool = _env_bool("OPENLANE_ENABLE_TRACE_ID_LOGS", True)
|
enable_trace_id_logs: bool = _env_bool("OPENLANE_ENABLE_TRACE_ID_LOGS", True)
|
||||||
runtime_config_file: str | None = _env_path_str("OPENLANE_RUNTIME_CONFIG_FILE")
|
runtime_config_file: str | None = _env_path_str("OPENLANE_RUNTIME_CONFIG_FILE") or "runtime_config.json"
|
||||||
fingerprint: FingerprintConfig = field(default_factory=FingerprintConfig)
|
fingerprint: FingerprintConfig = field(default_factory=FingerprintConfig)
|
||||||
openlane: OpenLaneConfig = field(default_factory=OpenLaneConfig)
|
openlane: OpenLaneConfig = field(default_factory=OpenLaneConfig)
|
||||||
database: DatabaseConfig = field(default_factory=DatabaseConfig)
|
database: DatabaseConfig = field(default_factory=DatabaseConfig)
|
||||||
|
|||||||
@@ -321,7 +321,9 @@ class OpenLaneAuthenticator:
|
|||||||
"https://app.openlane.com/api/_next/time",
|
"https://app.openlane.com/api/_next/time",
|
||||||
wait_until="domcontentloaded",
|
wait_until="domcontentloaded",
|
||||||
)
|
)
|
||||||
|
if self._is_api_authenticated(page):
|
||||||
return page
|
return page
|
||||||
|
logger.warning("Access token cookie exists but API returned unauthorized, will re-authenticate")
|
||||||
|
|
||||||
# Пробуем прямой refresh access_token без навигации.
|
# Пробуем прямой refresh access_token без навигации.
|
||||||
refresh_token = self._extract_refresh_token_from_cookies(context) or self.config.refresh_token
|
refresh_token = self._extract_refresh_token_from_cookies(context) or self.config.refresh_token
|
||||||
@@ -337,8 +339,10 @@ class OpenLaneAuthenticator:
|
|||||||
"https://app.openlane.com/api/_next/time",
|
"https://app.openlane.com/api/_next/time",
|
||||||
wait_until="domcontentloaded",
|
wait_until="domcontentloaded",
|
||||||
)
|
)
|
||||||
|
if self._is_api_authenticated(page):
|
||||||
logger.info("OpenLane session restored via direct Okta refresh")
|
logger.info("OpenLane session restored via direct Okta refresh")
|
||||||
return page
|
return page
|
||||||
|
logger.warning("Direct Okta refresh produced token but API still unauthorized")
|
||||||
logger.warning("Direct refresh failed, trying sign_in fallback")
|
logger.warning("Direct refresh failed, trying sign_in fallback")
|
||||||
|
|
||||||
# Если refresh_token был только в env, добавляем cookie вручную.
|
# Если refresh_token был только в env, добавляем cookie вручную.
|
||||||
@@ -355,7 +359,9 @@ class OpenLaneAuthenticator:
|
|||||||
self._wait_for_authenticated_session(page)
|
self._wait_for_authenticated_session(page)
|
||||||
self._persist_storage_state(context)
|
self._persist_storage_state(context)
|
||||||
self._maybe_rotate_token(context)
|
self._maybe_rotate_token(context)
|
||||||
|
if self._is_api_authenticated(page):
|
||||||
return page
|
return page
|
||||||
|
logger.warning("sign_in fallback completed but API is still unauthorized")
|
||||||
except OpenLaneAuthError:
|
except OpenLaneAuthError:
|
||||||
logger.warning("SPA refresh_token exchange failed")
|
logger.warning("SPA refresh_token exchange failed")
|
||||||
page.close()
|
page.close()
|
||||||
@@ -370,6 +376,43 @@ class OpenLaneAuthenticator:
|
|||||||
" 2. Set OPENLANE_USERNAME and OPENLANE_PASSWORD in .env"
|
" 2. Set OPENLANE_USERNAME and OPENLANE_PASSWORD in .env"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _is_api_authenticated(self, page: Page) -> bool:
|
||||||
|
"""Проверяет, что сессия реально авторизована для OpenLane API.
|
||||||
|
|
||||||
|
Нужна для случаев, когда access_token cookie есть (и даже не истёк),
|
||||||
|
но сервер уже считает сессию невалидной.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
status = page.evaluate(
|
||||||
|
"""async () => {
|
||||||
|
try {
|
||||||
|
const resp = await fetch('https://app.openlane.com/api/v4/search?page=1&per_page=1&source_tab=marketplace&sale_types=marketplace', {
|
||||||
|
method: 'GET',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json, application/vnd.backlotcars.v3'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return resp.status;
|
||||||
|
} catch (e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.warning("Failed to validate OpenLane API auth state", exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
code = int(status)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 401/403 — точно неавторизованы.
|
||||||
|
if code in (401, 403, 0):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def login(self, context: BrowserContext) -> Page:
|
def login(self, context: BrowserContext) -> Page:
|
||||||
self.validate_credentials()
|
self.validate_credentials()
|
||||||
page = context.new_page()
|
page = context.new_page()
|
||||||
|
|||||||
Reference in New Issue
Block a user