fix(server): return API token support for Relay servers sync

This commit is contained in:
ospab 2026-05-28 01:28:29 +03:00
parent 7b81f617ad
commit 03e113fccf
2 changed files with 32 additions and 9 deletions

View File

@ -42,6 +42,7 @@ pub struct ApiState {
pub user_stats: Arc<RwLock<HashMap<String, Arc<UserStats>>>>, pub user_stats: Arc<RwLock<HashMap<String, Arc<UserStats>>>>,
pub start_time: Instant, pub start_time: Instant,
pub session_token: Arc<RwLock<Option<String>>>, pub session_token: Arc<RwLock<Option<String>>>,
pub api_token: Option<String>,
pub webpath: String, pub webpath: String,
pub username: String, pub username: String,
pub password_hash: String, pub password_hash: String,
@ -59,6 +60,7 @@ pub struct ApiState {
pub struct ApiConfig { pub struct ApiConfig {
pub enabled: bool, pub enabled: bool,
pub bind: String, pub bind: String,
pub token: Option<String>,
#[serde(default)] #[serde(default)]
pub webpath: String, pub webpath: String,
#[serde(default)] #[serde(default)]
@ -72,6 +74,7 @@ impl Default for ApiConfig {
Self { Self {
enabled: false, enabled: false,
bind: "127.0.0.1:9090".to_string(), bind: "127.0.0.1:9090".to_string(),
token: None,
webpath: String::new(), webpath: String::new(),
username: String::new(), username: String::new(),
password_hash: String::new(), password_hash: String::new(),
@ -257,6 +260,7 @@ pub async fn start_api_server(
user_stats, user_stats,
start_time: Instant::now(), start_time: Instant::now(),
session_token: Arc::new(RwLock::new(None)), session_token: Arc::new(RwLock::new(None)),
api_token: config.token.clone(),
webpath: config.webpath.clone(), webpath: config.webpath.clone(),
username: config.username.clone(), username: config.username.clone(),
password_hash: config.password_hash.clone(), password_hash: config.password_hash.clone(),
@ -287,26 +291,40 @@ pub async fn start_api_server(
// ── Middleware: token check ────────────────────────────────────────────────── // ── Middleware: token check ──────────────────────────────────────────────────
fn check_token(state: &ApiState, headers: &axum::http::HeaderMap) -> bool { fn check_token(state: &ApiState, headers: &axum::http::HeaderMap) -> bool {
// Both session token (for web UI) and static API token (for relays) are checked
let mut allowed = false;
// If no credentials configured, panel is open (unsafe but possible) // If no credentials configured, panel is open (unsafe but possible)
if state.username.is_empty() && state.password_hash.is_empty() { if state.username.is_empty() && state.password_hash.is_empty() && state.api_token.is_none() {
return true; return true;
} }
match headers.get("authorization") { if let Some(value) = headers.get("authorization") {
Some(value) => { if let Ok(val) = value.to_str() {
let val = value.to_str().unwrap_or("");
if let Some(token) = val.strip_prefix("Bearer ") { if let Some(token) = val.strip_prefix("Bearer ") {
let current_session = state.session_token.read().unwrap().clone(); let current_session = state.session_token.read().unwrap().clone();
if let Some(session) = current_session { if let Some(session) = current_session {
if token == session { if token == session {
return true; allowed = true;
}
}
if let Some(ref api_tok) = state.api_token {
if token == api_tok {
allowed = true;
}
}
} else {
if let Some(ref api_tok) = state.api_token {
if val == api_tok {
allowed = true;
} }
} }
} }
false
} }
None => false,
} }
allowed
} }
async fn handle_login( async fn handle_login(
@ -904,6 +922,7 @@ mod tests {
user_stats: Arc::new(RwLock::new(HashMap::new())), user_stats: Arc::new(RwLock::new(HashMap::new())),
start_time: std::time::Instant::now(), start_time: std::time::Instant::now(),
session_token: Arc::new(RwLock::new(None)), session_token: Arc::new(RwLock::new(None)),
api_token: Some("test-token".to_string()),
webpath: webpath.to_string(), webpath: webpath.to_string(),
username: "admin".to_string(), username: "admin".to_string(),
password_hash: "hash".to_string(), password_hash: "hash".to_string(),

View File

@ -294,6 +294,7 @@ impl ListenConfig {
struct ApiConfig { struct ApiConfig {
enabled: Option<bool>, enabled: Option<bool>,
bind: Option<String>, bind: Option<String>,
token: Option<String>,
webpath: Option<String>, webpath: Option<String>,
username: Option<String>, username: Option<String>,
password_hash: Option<String>, password_hash: Option<String>,
@ -631,10 +632,12 @@ async fn run_app() -> Result<()> {
] ]
}}, }},
// Web control panel // Web control panel & Management API
"api": {{ "api": {{
"enabled": false, "enabled": false,
"bind": "0.0.0.0:9090", "bind": "0.0.0.0:9090",
// Static API token for Relay servers (optional)
"token": "",
// Secret URL path to hide panel from scanners (e.g. "mySecret123") // Secret URL path to hide panel from scanners (e.g. "mySecret123")
"webpath": "", "webpath": "",
// Login credentials for web panel (password stored as SHA256 hash) // Login credentials for web panel (password stored as SHA256 hash)
@ -904,6 +907,7 @@ async fn run_app() -> Result<()> {
let api_config = server_cfg.api.map(|a| ostp_server::ApiConfig { let api_config = server_cfg.api.map(|a| ostp_server::ApiConfig {
enabled: a.enabled.unwrap_or(false), enabled: a.enabled.unwrap_or(false),
bind: a.bind.unwrap_or_else(|| "127.0.0.1:9090".to_string()), bind: a.bind.unwrap_or_else(|| "127.0.0.1:9090".to_string()),
token: a.token.clone(),
webpath: a.webpath.unwrap_or_default(), webpath: a.webpath.unwrap_or_default(),
username: a.username.unwrap_or_default(), username: a.username.unwrap_or_default(),
password_hash: a.password_hash.unwrap_or_default(), password_hash: a.password_hash.unwrap_or_default(),