Prevent env autofill from overwriting OKX API keys when saving hedge settings.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2735,9 +2735,12 @@ def resolve_order_entry_price(order_resp, exchange_symbol, fallback_price):
|
|||||||
|
|
||||||
|
|
||||||
def get_contract_size(exchange_symbol):
|
def get_contract_size(exchange_symbol):
|
||||||
ensure_markets_loaded()
|
try:
|
||||||
market = exchange.market(exchange_symbol)
|
ensure_markets_loaded()
|
||||||
return float(market.get("contractSize") or 1)
|
market = exchange.market(exchange_symbol)
|
||||||
|
return float(market.get("contractSize") or 1)
|
||||||
|
except Exception:
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
|
||||||
def parse_positive_float(value):
|
def parse_positive_float(value):
|
||||||
@@ -6945,11 +6948,14 @@ def api_account_snapshot():
|
|||||||
except Exception:
|
except Exception:
|
||||||
return exchange.fetch_positions() or []
|
return exchange.fetch_positions() or []
|
||||||
|
|
||||||
unrealized_pnl = resolve_instance_unrealized_pnl(
|
try:
|
||||||
_okx_positions,
|
unrealized_pnl = resolve_instance_unrealized_pnl(
|
||||||
active_pnl_rows,
|
_okx_positions,
|
||||||
get_live_position_exchange_metrics,
|
active_pnl_rows,
|
||||||
)
|
get_live_position_exchange_metrics,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
unrealized_pnl = None
|
||||||
options_unrealized_pnl = None
|
options_unrealized_pnl = None
|
||||||
if OKX_OPTIONS_ENABLED and exchange_options.apiKey:
|
if OKX_OPTIONS_ENABLED and exchange_options.apiKey:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -202,8 +202,21 @@
|
|||||||
input = document.createElement("input");
|
input = document.createElement("input");
|
||||||
input.id = "env-f-" + field.key;
|
input.id = "env-f-" + field.key;
|
||||||
input.type = "password";
|
input.type = "password";
|
||||||
input.autocomplete = "off";
|
// 防止浏览器把登录密码自动填进 API Key/Secret(保存对冲开关时曾误写入密钥)
|
||||||
|
input.autocomplete = "new-password";
|
||||||
|
input.setAttribute("data-lpignore", "true");
|
||||||
|
input.setAttribute("data-1p-ignore", "true");
|
||||||
|
input.setAttribute("data-form-type", "other");
|
||||||
|
input.readOnly = true;
|
||||||
|
input.addEventListener("focus", function () {
|
||||||
|
input.readOnly = false;
|
||||||
|
});
|
||||||
if (field.sensitive) {
|
if (field.sensitive) {
|
||||||
|
input.dataset.envSensitive = "1";
|
||||||
|
input.dataset.envDirty = "0";
|
||||||
|
input.addEventListener("input", function () {
|
||||||
|
input.dataset.envDirty = "1";
|
||||||
|
});
|
||||||
if (field.has_value) {
|
if (field.has_value) {
|
||||||
const cur = document.createElement("div");
|
const cur = document.createElement("div");
|
||||||
cur.className = "env-sensitive-current muted";
|
cur.className = "env-sensitive-current muted";
|
||||||
@@ -219,6 +232,7 @@
|
|||||||
input.placeholder = field.has_value ? "修改时填写新值,留空不修改" : "请输入";
|
input.placeholder = field.has_value ? "修改时填写新值,留空不修改" : "请输入";
|
||||||
} else {
|
} else {
|
||||||
input.type = "text";
|
input.type = "text";
|
||||||
|
input.autocomplete = "off";
|
||||||
input.value = field.current || field.default || "";
|
input.value = field.current || field.default || "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,6 +323,10 @@
|
|||||||
const values = {};
|
const values = {};
|
||||||
const scope = root || document;
|
const scope = root || document;
|
||||||
scope.querySelectorAll(".env-field-input[data-env-key]").forEach((el) => {
|
scope.querySelectorAll(".env-field-input[data-env-key]").forEach((el) => {
|
||||||
|
if (el.dataset.envSensitive === "1" && el.dataset.envDirty !== "1") {
|
||||||
|
// 未改动过的敏感项不提交,避免浏览器自动填充覆盖已有密钥
|
||||||
|
return;
|
||||||
|
}
|
||||||
values[el.dataset.envKey] = el.value;
|
values[el.dataset.envKey] = el.value;
|
||||||
});
|
});
|
||||||
return values;
|
return values;
|
||||||
|
|||||||
Vendored
+4
@@ -275,6 +275,10 @@ def validate_env_updates(groups: list[dict], updates: dict[str, str]) -> tuple[d
|
|||||||
val = str(value).strip()
|
val = str(value).strip()
|
||||||
if allowed[key].get("sensitive") and (val == "" or (val.startswith("****") and len(val) <= 8)):
|
if allowed[key].get("sensitive") and (val == "" or (val.startswith("****") and len(val) <= 8)):
|
||||||
continue
|
continue
|
||||||
|
# API Key 被密码管理器/自动填充成登录密码时通常很短;OKX Key 一般为 36 位
|
||||||
|
if key.endswith("_API_KEY") and 0 < len(val) < 16:
|
||||||
|
errors.append(f"{key} 长度异常,疑似自动填充;留空则不修改已有密钥")
|
||||||
|
continue
|
||||||
ftype = allowed[key].get("type")
|
ftype = allowed[key].get("type")
|
||||||
if ftype == "bool":
|
if ftype == "bool":
|
||||||
low = val.lower()
|
low = val.lower()
|
||||||
|
|||||||
@@ -105,18 +105,21 @@ def total_funds_usdt(
|
|||||||
options_funding_usdt: float | None = None,
|
options_funding_usdt: float | None = None,
|
||||||
options_trading_usdt: float | None = None,
|
options_trading_usdt: float | None = None,
|
||||||
) -> float | None:
|
) -> float | None:
|
||||||
if funding_usdt is None:
|
parts = [
|
||||||
|
funding_usdt,
|
||||||
|
trading_usdt,
|
||||||
|
options_funding_usdc,
|
||||||
|
options_funding_usdt,
|
||||||
|
options_trading_usdc,
|
||||||
|
options_trading_usdt,
|
||||||
|
]
|
||||||
|
if all(v is None for v in parts):
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
total = float(funding_usdt) + float(trading_usdt or 0)
|
total = 0.0
|
||||||
if options_funding_usdc is not None:
|
for v in parts:
|
||||||
total += float(options_funding_usdc)
|
if v is not None:
|
||||||
if options_funding_usdt is not None:
|
total += float(v)
|
||||||
total += float(options_funding_usdt)
|
|
||||||
if options_trading_usdc is not None:
|
|
||||||
total += float(options_trading_usdc)
|
|
||||||
if options_trading_usdt is not None:
|
|
||||||
total += float(options_trading_usdt)
|
|
||||||
return round(total, 2)
|
return round(total, 2)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj
|
|||||||
<script>
|
<script>
|
||||||
window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
|
window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
|
||||||
</script>
|
</script>
|
||||||
<script src="/static/instance_settings_prefs.js?v=12"></script>
|
<script src="/static/instance_settings_prefs.js?v=13"></script>
|
||||||
<script src="/static/instance_live.js?v=5"></script>
|
<script src="/static/instance_live.js?v=5"></script>
|
||||||
<script src="/static/instance_embed.js?v=23"></script>
|
<script src="/static/instance_embed.js?v=23"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -2160,6 +2160,6 @@ setInterval(refreshPriceSnapshotConditional, {{ price_refresh_seconds * 1000 }})
|
|||||||
<script>
|
<script>
|
||||||
window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
|
window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
|
||||||
</script>
|
</script>
|
||||||
<script src="/static/instance_settings_prefs.js?v=12"></script>
|
<script src="/static/instance_settings_prefs.js?v=13"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user