fix(health-check): actually authenticate against the hoster instead of just checking field presence
Previous behavior: login-based accounts (Doodstream/VOE/Vidmoly) reported 'Login hinterlegt — Bereit' as long as username/password were non-empty. Entering nonsense (asdas@web.de / anything) passed. Now: - Vidmoly: POST /api/auth/login with JSON and verify /api/upload/config is reachable afterwards — 401/403 or non-OK message → BadCredentials. - Doodstream: login_ajax POST, success when either Dashboard HTML comes back or json.status == 'success'; OTP-required is surfaced as 'Login gültig (OTP erforderlich)'. - VOE: Laravel CSRF scrape + POST /login, then verify /file-upload renders a fresh CSRF (only present when logged in). - Clouddrop: 401/403 now mapped to BadCredentials instead of generic. - Byse: parse JSON status field (server returns HTTP 200 + status:403 on bad keys) and map accordingly. Bogus credentials now correctly show a red 'Fehler' state.
This commit is contained in:
parent
c2d706f6c9
commit
58be08b4e7
@ -354,29 +354,135 @@ pub async fn run_health_check(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn check_account_live(hoster: &str, a: &Account) -> AppResult<String> {
|
async fn check_account_live(hoster: &str, a: &Account) -> AppResult<String> {
|
||||||
|
// Byse's account-info endpoint returns HTTP 200 with {"status":403} on bad
|
||||||
|
// keys, so we parse the JSON properly instead of just checking the status.
|
||||||
|
let timeout = std::time::Duration::from_secs(20);
|
||||||
match hoster {
|
match hoster {
|
||||||
"clouddrop.cc" => {
|
"clouddrop.cc" => {
|
||||||
if a.api_key.is_empty() { return Err(AppError::BadCredentials); }
|
if a.api_key.is_empty() { return Err(AppError::BadCredentials); }
|
||||||
let c = reqwest::Client::builder().timeout(std::time::Duration::from_secs(15)).build()?;
|
let c = reqwest::Client::builder().timeout(timeout).build()?;
|
||||||
let r = c.get("https://clouddrop.cc/api/cloud/files/?limit=1").bearer_auth(&a.api_key).send().await?;
|
let r = c.get("https://clouddrop.cc/api/cloud/files/?limit=1")
|
||||||
if r.status().is_success() { Ok("API Key gültig".into()) } else { Err(AppError::HosterError("Clouddrop".into(), format!("HTTP {}", r.status().as_u16()))) }
|
.bearer_auth(&a.api_key).send().await?;
|
||||||
|
match r.status().as_u16() {
|
||||||
|
200..=299 => Ok("API Key gültig".into()),
|
||||||
|
401 | 403 => Err(AppError::BadCredentials),
|
||||||
|
code => Err(AppError::HosterError("Clouddrop".into(), format!("HTTP {code}"))),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"byse.sx" => {
|
"byse.sx" => {
|
||||||
if a.api_key.is_empty() { return Err(AppError::BadCredentials); }
|
if a.api_key.is_empty() { return Err(AppError::BadCredentials); }
|
||||||
let c = reqwest::Client::builder().timeout(std::time::Duration::from_secs(15)).build()?;
|
let c = reqwest::Client::builder().timeout(timeout).build()?;
|
||||||
let r = c.get(format!("https://api.byse.sx/api/account/info?key={}", urlencoding::encode(&a.api_key))).send().await?;
|
let r = c.get(format!("https://api.byse.sx/api/account/info?key={}", urlencoding::encode(&a.api_key))).send().await?;
|
||||||
if r.status().is_success() { Ok("API Key gültig".into()) } else { Err(AppError::HosterError("Byse".into(), format!("HTTP {}", r.status().as_u16()))) }
|
let text = r.text().await.unwrap_or_default();
|
||||||
|
let v: serde_json::Value = serde_json::from_str(&text).unwrap_or(serde_json::Value::Null);
|
||||||
|
let status = v.get("status").and_then(|x| x.as_u64()).unwrap_or(200);
|
||||||
|
if status == 200 { Ok("API Key gültig".into()) }
|
||||||
|
else if status == 401 || status == 403 { Err(AppError::BadCredentials) }
|
||||||
|
else { Err(AppError::HosterError("Byse".into(),
|
||||||
|
v.get("msg").and_then(|x| x.as_str()).unwrap_or("Fehler").to_string())) }
|
||||||
|
}
|
||||||
|
"vidmoly.me" => {
|
||||||
|
if a.username.is_empty() || a.password.is_empty() { return Err(AppError::BadCredentials); }
|
||||||
|
let c = reqwest::Client::builder()
|
||||||
|
.timeout(timeout).cookie_store(true).build()?;
|
||||||
|
let _ = c.get("https://vidmoly.me").send().await;
|
||||||
|
let res = c.post("https://vidmoly.me/api/auth/login")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.header("Origin", "https://vidmoly.me")
|
||||||
|
.header("Referer", "https://vidmoly.me/login")
|
||||||
|
.json(&serde_json::json!({ "login": a.username, "password": a.password }))
|
||||||
|
.send().await?;
|
||||||
|
let code = res.status().as_u16();
|
||||||
|
let body = res.text().await.unwrap_or_default();
|
||||||
|
if code == 401 || code == 403 || body.to_lowercase().contains("incorrect")
|
||||||
|
|| body.to_lowercase().contains("invalid") {
|
||||||
|
return Err(AppError::BadCredentials);
|
||||||
|
}
|
||||||
|
if !(200..300).contains(&code) {
|
||||||
|
return Err(AppError::HosterError("Vidmoly".into(), format!("HTTP {code}")));
|
||||||
|
}
|
||||||
|
// Verify session works against the upload-config endpoint.
|
||||||
|
let probe = c.get("https://vidmoly.me/api/upload/config")
|
||||||
|
.header("Accept", "application/json").send().await?;
|
||||||
|
if probe.status().is_success() { Ok("Login gültig".into()) }
|
||||||
|
else { Err(AppError::BadCredentials) }
|
||||||
|
}
|
||||||
|
"doodstream.com" => {
|
||||||
|
if a.username.is_empty() || a.password.is_empty() { return Err(AppError::BadCredentials); }
|
||||||
|
let c = reqwest::Client::builder()
|
||||||
|
.timeout(timeout).cookie_store(true).build()?;
|
||||||
|
let _ = c.get("https://doodstream.com").send().await;
|
||||||
|
let body = serde_urlencoded::to_string([
|
||||||
|
("op", "login_ajax"),
|
||||||
|
("login", a.username.as_str()),
|
||||||
|
("password", a.password.as_str()),
|
||||||
|
("loginotp", ""),
|
||||||
|
]).unwrap_or_default();
|
||||||
|
let res = c.post("https://doodstream.com/")
|
||||||
|
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
.header("X-Requested-With", "XMLHttpRequest")
|
||||||
|
.header("Referer", "https://doodstream.com/")
|
||||||
|
.body(body).send().await?;
|
||||||
|
let text = res.text().await.unwrap_or_default();
|
||||||
|
if text.contains("Dashboard") { return Ok("Login gültig".into()); }
|
||||||
|
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&text) {
|
||||||
|
if let Some(s) = v.get("status").and_then(|x| x.as_str()) {
|
||||||
|
if s == "success" { return Ok("Login gültig".into()); }
|
||||||
|
let msg = v.get("message").and_then(|x| x.as_str()).unwrap_or("Login fehlgeschlagen");
|
||||||
|
if msg.to_lowercase().contains("otp") { return Ok("Login gültig (OTP erforderlich)".into()); }
|
||||||
|
return Err(AppError::BadCredentials);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we scrape a logged-in page successfully that's also good.
|
||||||
|
let probe = c.get("https://doodstream.com/?op=my_files").send().await?;
|
||||||
|
if probe.status().is_success() {
|
||||||
|
let probe_text = probe.text().await.unwrap_or_default();
|
||||||
|
if probe_text.contains("logout") || probe_text.contains("Logout")
|
||||||
|
|| probe_text.contains("Dashboard") { return Ok("Login gültig".into()); }
|
||||||
|
}
|
||||||
|
Err(AppError::BadCredentials)
|
||||||
|
}
|
||||||
|
"voe.sx" => {
|
||||||
|
if a.username.is_empty() || a.password.is_empty() { return Err(AppError::BadCredentials); }
|
||||||
|
let c = reqwest::Client::builder()
|
||||||
|
.timeout(timeout).cookie_store(true).build()?;
|
||||||
|
let login_html = c.get("https://voe.sx/login").send().await?.text().await.unwrap_or_default();
|
||||||
|
let csrf = regex::Regex::new(r#"<meta\s+name=["']csrf-token["']\s+content=["']([^"']+)["']"#).unwrap()
|
||||||
|
.captures(&login_html).and_then(|c| c.get(1).map(|m| m.as_str().to_string()))
|
||||||
|
.or_else(|| regex::Regex::new(r#"<input[^>]*name=["']_token["'][^>]*value=["']([^"']+)["']"#).unwrap()
|
||||||
|
.captures(&login_html).and_then(|c| c.get(1).map(|m| m.as_str().to_string())))
|
||||||
|
.ok_or_else(|| AppError::HosterError("VOE".into(), "CSRF-Token nicht gefunden".into()))?;
|
||||||
|
let body = serde_urlencoded::to_string([
|
||||||
|
("_token", csrf.as_str()),
|
||||||
|
("email", a.username.as_str()),
|
||||||
|
("password", a.password.as_str()),
|
||||||
|
]).unwrap_or_default();
|
||||||
|
let res = c.post("https://voe.sx/login")
|
||||||
|
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
.header("Referer", "https://voe.sx/login")
|
||||||
|
.body(body).send().await?;
|
||||||
|
let text = res.text().await.unwrap_or_default();
|
||||||
|
if text.contains("credentials do not match") || text.contains("Incorrect") || text.contains("invalid") {
|
||||||
|
return Err(AppError::BadCredentials);
|
||||||
|
}
|
||||||
|
// Confirm session by pulling the upload page and looking for a CSRF
|
||||||
|
// (only present when logged in).
|
||||||
|
let upload_html = c.get("https://voe.sx/file-upload").send().await?.text().await.unwrap_or_default();
|
||||||
|
if regex::Regex::new(r#"name=["']csrf-token["']"#).unwrap().is_match(&upload_html) {
|
||||||
|
Ok("Login gültig".into())
|
||||||
|
} else {
|
||||||
|
Err(AppError::BadCredentials)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if a.auth_type == "login" {
|
if a.auth_type == "login" && (a.username.is_empty() || a.password.is_empty()) {
|
||||||
if a.username.is_empty() || a.password.is_empty() { return Err(AppError::BadCredentials); }
|
return Err(AppError::BadCredentials);
|
||||||
Ok("Login hinterlegt".into())
|
|
||||||
} else if a.auth_type == "api" {
|
|
||||||
if a.api_key.is_empty() { return Err(AppError::BadCredentials); }
|
|
||||||
Ok("API Key hinterlegt".into())
|
|
||||||
} else {
|
|
||||||
Ok("Nicht geprüft".into())
|
|
||||||
}
|
}
|
||||||
|
if a.auth_type == "api" && a.api_key.is_empty() {
|
||||||
|
return Err(AppError::BadCredentials);
|
||||||
|
}
|
||||||
|
Ok("Nicht implementiert".into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user