Compare commits
55
Commits
main
..
cccf6b3916
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cccf6b3916 | ||
|
|
55203d33ea | ||
|
|
5999c2e42d | ||
|
|
a8c5f8aa41 | ||
|
|
05d1ffb5ac | ||
|
|
3c3aea8eb3 | ||
|
|
37d01c4ba1 | ||
|
|
6165c65159 | ||
|
|
acbb045b6e | ||
|
|
d9111be2d2 | ||
|
|
cac68bda4c | ||
|
|
0add3913eb | ||
|
|
6a334d3c64 | ||
|
|
d51216ddb7 | ||
|
|
9337344182 | ||
|
|
c729f971e3 | ||
|
|
eaafbd5816 | ||
|
|
a4c93a1df1 | ||
|
|
b9557922ed | ||
|
|
85b4ff8502 | ||
|
|
6134b10fd0 | ||
|
|
05b83b5518 | ||
|
|
b1aeb966ac | ||
|
|
1acfafe665 | ||
|
|
2cbefbde93 | ||
|
|
db2fa5ec17 | ||
|
|
18dab6d48d | ||
|
|
3870cf4d94 | ||
|
|
62aafae793 | ||
|
|
acf30fcc20 | ||
|
|
7b1501008e | ||
|
|
6d1702faae | ||
|
|
2931eee0a7 | ||
|
|
f6d7c8ea60 | ||
|
|
eb9fb48ea0 | ||
|
|
042ffdcfbb | ||
|
|
437aedad45 | ||
|
|
9f703696d8 | ||
|
|
3992067a94 | ||
|
|
e5700f9623 | ||
|
|
f4b9d20191 | ||
|
|
6e97991c68 | ||
|
|
90bd4756b3 | ||
|
|
64b7c760d9 | ||
|
|
f161071e07 | ||
|
|
3b218534ec | ||
|
|
54fea100dc | ||
|
|
447a88feed | ||
|
|
b2d3902bcc | ||
|
|
d1844ba625 | ||
|
|
ff2a1c7ac8 | ||
|
|
0010abdf08 | ||
|
|
f96e5ca5f8 | ||
|
|
21bdf17f35 | ||
|
|
78b52b265f |
@@ -2,7 +2,6 @@
|
|||||||
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") or "runtime_config.json"
|
runtime_config_file: str | None = _env_path_str("OPENLANE_RUNTIME_CONFIG_FILE")
|
||||||
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,9 +321,7 @@ 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
|
||||||
@@ -339,10 +337,8 @@ 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 вручную.
|
||||||
@@ -359,9 +355,7 @@ 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()
|
||||||
@@ -376,43 +370,6 @@ 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