mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-03 14:32:08 +02:00
fix: panel api request to store new created user
This commit is contained in:
@@ -211,6 +211,34 @@ fn build_panel_url(base_url: &str, query_params: &[(String, String)]) -> Result<
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
fn sanitize_panel_api_json_for_log(value: &Value) -> Value {
|
||||
match value {
|
||||
Value::Array(arr) => Value::Array(arr.iter().map(sanitize_panel_api_json_for_log).collect()),
|
||||
Value::Object(obj) => {
|
||||
let mut out = serde_json::Map::with_capacity(obj.len());
|
||||
for (k, v) in obj {
|
||||
if k.eq_ignore_ascii_case("api_key") || k.eq_ignore_ascii_case("apikey") || k.eq_ignore_ascii_case("token") {
|
||||
out.insert(k.clone(), Value::String("***".to_string()));
|
||||
continue;
|
||||
}
|
||||
if k.eq_ignore_ascii_case("username") || k.eq_ignore_ascii_case("password") {
|
||||
out.insert(k.clone(), Value::String("***".to_string()));
|
||||
continue;
|
||||
}
|
||||
if k.eq_ignore_ascii_case("url") {
|
||||
if let Some(s) = v.as_str() {
|
||||
out.insert(k.clone(), Value::String(sanitize_sensitive_info(s).into_owned()));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
out.insert(k.clone(), sanitize_panel_api_json_for_log(v));
|
||||
}
|
||||
Value::Object(out)
|
||||
}
|
||||
other => other.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn panel_get_json(app_state: &AppState, url: Url) -> Result<Value, TuliproxError> {
|
||||
let client = app_state.http_client.load();
|
||||
let sanitized = sanitize_sensitive_info(url.as_str());
|
||||
@@ -227,6 +255,10 @@ async fn panel_get_json(app_state: &AppState, url: Url) -> Result<Value, Tulipro
|
||||
.map_err(|e| TuliproxError::new(TuliproxErrorKind::Info, format!("panel_api read response failed: {e}")))?;
|
||||
let json: Value = serde_json::from_str(&body)
|
||||
.map_err(|e| TuliproxError::new(TuliproxErrorKind::Info, format!("panel_api invalid json (http {status}): {e}")))?;
|
||||
let json_for_log = sanitize_panel_api_json_for_log(&json);
|
||||
if let Ok(json_str) = serde_json::to_string(&json_for_log) {
|
||||
debug_if_enabled!("panel_api response (http {}): {}", status, sanitize_sensitive_info(&json_str));
|
||||
}
|
||||
Ok(json)
|
||||
}
|
||||
|
||||
@@ -395,10 +427,29 @@ async fn patch_source_yml_add_alias(
|
||||
};
|
||||
|
||||
let aliases_key = serde_yaml::Value::String("aliases".to_string());
|
||||
if !inp_map.contains_key(&aliases_key) {
|
||||
inp_map.insert(aliases_key.clone(), serde_yaml::Value::Sequence(vec![]));
|
||||
let aliases_value = inp_map
|
||||
.entry(aliases_key.clone())
|
||||
.or_insert_with(|| serde_yaml::Value::Sequence(vec![]));
|
||||
match aliases_value {
|
||||
serde_yaml::Value::Sequence(_) => {}
|
||||
serde_yaml::Value::Null => {
|
||||
*aliases_value = serde_yaml::Value::Sequence(vec![]);
|
||||
}
|
||||
serde_yaml::Value::Mapping(_) => {
|
||||
// Some users might have a single alias stored as a mapping instead of a list.
|
||||
// Normalize to a list so we can append safely.
|
||||
let old = std::mem::replace(aliases_value, serde_yaml::Value::Sequence(vec![]));
|
||||
if let serde_yaml::Value::Mapping(map) = old {
|
||||
if let serde_yaml::Value::Sequence(seq) = aliases_value {
|
||||
seq.push(serde_yaml::Value::Mapping(map));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return create_tuliprox_error_result!(TuliproxErrorKind::Info, "panel_api: input.aliases is not a list in source.yml");
|
||||
}
|
||||
}
|
||||
let Some(alias_seq) = inp_map.get_mut(&aliases_key).and_then(|v| v.as_sequence_mut()) else {
|
||||
let Some(alias_seq) = aliases_value.as_sequence_mut() else {
|
||||
return create_tuliprox_error_result!(TuliproxErrorKind::Info, "panel_api: input.aliases is not a list in source.yml");
|
||||
};
|
||||
|
||||
@@ -452,12 +503,24 @@ async fn patch_source_yml_update_exp_date(
|
||||
if account_name == input_name {
|
||||
inp_map.insert(serde_yaml::Value::String("exp_date".to_string()), serde_yaml::Value::Number(exp_date.into()));
|
||||
inp_map.insert(serde_yaml::Value::String("enabled".to_string()), serde_yaml::Value::Bool(true));
|
||||
} else if let Some(aliases) = inp_map.get_mut(serde_yaml::Value::String("aliases".to_string())).and_then(|v| v.as_sequence_mut()) {
|
||||
for a in aliases.iter_mut() {
|
||||
let Some(a_map) = a.as_mapping_mut() else { continue; };
|
||||
let a_name = a_map.get(serde_yaml::Value::String("name".to_string())).and_then(|v| v.as_str());
|
||||
if a_name == Some(account_name) {
|
||||
a_map.insert(serde_yaml::Value::String("exp_date".to_string()), serde_yaml::Value::Number(exp_date.into()));
|
||||
} else if let Some(aliases_value) = inp_map.get_mut(serde_yaml::Value::String("aliases".to_string())) {
|
||||
// Normalize aliases to a sequence if the file contains a single mapping.
|
||||
if matches!(aliases_value, serde_yaml::Value::Mapping(_)) {
|
||||
let old = std::mem::replace(aliases_value, serde_yaml::Value::Sequence(vec![]));
|
||||
if let serde_yaml::Value::Mapping(map) = old {
|
||||
if let serde_yaml::Value::Sequence(seq) = aliases_value {
|
||||
seq.push(serde_yaml::Value::Mapping(map));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(aliases) = aliases_value.as_sequence_mut() {
|
||||
for a in aliases.iter_mut() {
|
||||
let Some(a_map) = a.as_mapping_mut() else { continue; };
|
||||
let a_name = a_map.get(serde_yaml::Value::String("name".to_string())).and_then(|v| v.as_str());
|
||||
if a_name == Some(account_name) {
|
||||
a_map.insert(serde_yaml::Value::String("exp_date".to_string()), serde_yaml::Value::Number(exp_date.into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,7 +325,7 @@ where
|
||||
.map_err(|err| create_tuliprox_error!(TuliproxErrorKind::Info, "Could not write file {}: {}", &path.to_str().unwrap_or("?"), err))
|
||||
}
|
||||
|
||||
pub fn format_sources_yaml_panel_api_query_params_flow_style(yaml: &str) -> String {
|
||||
fn format_sources_yaml_panel_api_query_params_flow_style_impl(yaml: &str) -> String {
|
||||
let has_trailing_newline = yaml.ends_with('\n');
|
||||
let lines: Vec<&str> = yaml.split_terminator('\n').collect();
|
||||
let mut out: Vec<String> = Vec::with_capacity(lines.len());
|
||||
@@ -417,6 +417,97 @@ pub fn format_sources_yaml_panel_api_query_params_flow_style(yaml: &str) -> Stri
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_yaml_key_value_line(s: &str) -> Option<(String, String)> {
|
||||
let (key, value) = s.split_once(':')?;
|
||||
let key = key.trim();
|
||||
if key.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let value = value.trim();
|
||||
let value = if value.is_empty() { "null" } else { value };
|
||||
Some((key.to_string(), value.to_string()))
|
||||
}
|
||||
|
||||
fn format_sources_yaml_aliases_flow_style_impl(yaml: &str) -> String {
|
||||
let has_trailing_newline = yaml.ends_with('\n');
|
||||
let lines: Vec<&str> = yaml.split_terminator('\n').collect();
|
||||
let mut out: Vec<String> = Vec::with_capacity(lines.len());
|
||||
|
||||
let mut aliases_indent: Option<usize> = None;
|
||||
|
||||
let mut i = 0usize;
|
||||
while i < lines.len() {
|
||||
let line = lines[i];
|
||||
let trimmed = line.trim_start();
|
||||
let indent = line.len().saturating_sub(trimmed.len());
|
||||
|
||||
if !trimmed.is_empty() && !trimmed.starts_with('#') {
|
||||
if let Some(ai) = aliases_indent {
|
||||
if indent < ai || (indent == ai && trimmed != "aliases:" && !trimmed.starts_with("- ")) {
|
||||
aliases_indent = None;
|
||||
}
|
||||
}
|
||||
if trimmed == "aliases:" {
|
||||
aliases_indent = Some(indent);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert:
|
||||
// aliases:
|
||||
// - name: foo
|
||||
// url: http://...
|
||||
// Into:
|
||||
// - { name: foo, url: http://... }
|
||||
if aliases_indent.is_some() && trimmed.starts_with("- ") && !trimmed.starts_with("- {") {
|
||||
let item_indent = indent;
|
||||
let first = trimmed.strip_prefix("- ").unwrap_or(trimmed);
|
||||
if let Some((k, v)) = parse_yaml_key_value_line(first) {
|
||||
let mut parts: Vec<(String, String)> = vec![(k, v)];
|
||||
let mut j = i + 1;
|
||||
while j < lines.len() {
|
||||
let l2 = lines[j];
|
||||
let t2 = l2.trim_start();
|
||||
let ind2 = l2.len().saturating_sub(t2.len());
|
||||
if t2.is_empty() || t2.starts_with('#') {
|
||||
break;
|
||||
}
|
||||
if ind2 == item_indent.saturating_add(2) && !t2.starts_with("- ") {
|
||||
if let Some((k2, v2)) = parse_yaml_key_value_line(t2) {
|
||||
parts.push((k2, v2));
|
||||
j += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let formatted = parts
|
||||
.iter()
|
||||
.map(|(kk, vv)| format!("{kk}: {vv}"))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
out.push(format!("{}- {{ {} }}", " ".repeat(item_indent), formatted));
|
||||
i = j;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
out.push(line.to_string());
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if has_trailing_newline {
|
||||
out.join("\n") + "\n"
|
||||
} else {
|
||||
out.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_sources_yaml_panel_api_query_params_flow_style(yaml: &str) -> String {
|
||||
let yaml = format_sources_yaml_panel_api_query_params_flow_style_impl(yaml);
|
||||
format_sources_yaml_aliases_flow_style_impl(&yaml)
|
||||
}
|
||||
|
||||
pub async fn save_api_proxy(file_path: &str, backup_dir: &str, config: &ApiProxyConfigDto) -> Result<(), TuliproxError> {
|
||||
write_config_file(file_path, backup_dir, config, "api-proxy.yml").await
|
||||
}
|
||||
@@ -492,4 +583,25 @@ mod tests {
|
||||
assert!(out.contains("- { key: type, value: m3u }"));
|
||||
assert!(!out.contains("- key: api_key\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sources_yaml_aliases_flow_style() {
|
||||
let input = r"sources:
|
||||
- inputs:
|
||||
- name: demo
|
||||
aliases:
|
||||
- name: demo-u1
|
||||
url: http://line.example.invalid
|
||||
username: u1
|
||||
password: p1
|
||||
priority: 0
|
||||
max_connections: 1
|
||||
exp_date: 123
|
||||
";
|
||||
let out = format_sources_yaml_panel_api_query_params_flow_style(input);
|
||||
assert!(
|
||||
out.contains("- { name: demo-u1, url: http://line.example.invalid, username: u1, password: p1, priority: 0, max_connections: 1, exp_date: 123 }"),
|
||||
"out:\n{out}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user