use reqwest::blocking::Client;
use serde_json::Value;
use crate::config::{FirebaseProjectInfo, TestResult};
pub fn test_anonymous_signup(
client: &Client,
key: &str,
info: &mut FirebaseProjectInfo,
) -> TestResult {
let url = format!(
"https://identitytoolkit.googleapis.com/v1/accounts:signUp?key={}",
key
);
let resp = match client
.post(&url)
.json(&serde_json::json!({"returnSecureToken": true}))
.send()
{
Ok(r) => r,
Err(e) => return TestResult::fail(None, e.to_string(), None),
};
let status = resp.status().as_u16();
let data: Value = resp.json().unwrap_or_default();
if status == 200 {
info.anonymous_auth_enabled = Some(true);
let mut result = TestResult::ok(status, "Anonymous signup ENABLED — got valid idToken");
if let Some(id) = data.get("localId").and_then(|v| v.as_str()) {
result = result.with_extra("localId", id);
}
if let Some(tok) = data.get("idToken").and_then(|v| v.as_str()) {
let preview = if tok.len() > 50 {
format!("{}...", &tok[..50])
} else {
tok.to_string()
};
result = result.with_extra("idToken", preview);
}
result
} else if data.to_string().contains("OPERATION_NOT_ALLOWED") {
info.anonymous_auth_enabled = Some(false);
TestResult::fail(
Some(status),
"OPERATION_NOT_ALLOWED",
Some("Anonymous auth is DISABLED for this project".into()),
)
} else {
let msg = extract_error_message(&data);
TestResult::fail(Some(status), msg, None)
}
}
pub fn test_email_signup_capability(
client: &Client,
key: &str,
info: &mut FirebaseProjectInfo,
) -> TestResult {
let url = format!(
"https://identitytoolkit.googleapis.com/v1/accounts:signUp?key={}",
key
);
let body = serde_json::json!({
"email": "test_capability_check_12345@nonexistent-domain-xyz.invalid",
"password": "TestPassword123!",
"returnSecureToken": true
});
let resp = match client.post(&url).json(&body).send() {
Ok(r) => r,
Err(e) => return TestResult::fail(None, e.to_string(), None),
};
let status = resp.status().as_u16();
let data: Value = resp.json().unwrap_or_default();
let error_msg = extract_error_message(&data);
if status == 200 {
info.email_auth_enabled = Some(true);
info.signup_disabled = Some(false);
TestResult::ok(status, "Email signup ENABLED — account creation allowed")
} else if error_msg.contains("OPERATION_NOT_ALLOWED") {
info.email_auth_enabled = Some(false);
TestResult::fail(
Some(status),
"OPERATION_NOT_ALLOWED",
Some("Email/password auth is DISABLED".into()),
)
} else if error_msg.contains("ADMIN_ONLY_OPERATION")
|| error_msg.to_uppercase().contains("DISALLOWED")
{
info.signup_disabled = Some(true);
info.email_auth_enabled = Some(true);
TestResult::fail(
Some(status),
&error_msg,
Some("Email auth enabled but signup RESTRICTED (admin only)".into()),
)
} else if error_msg.contains("INVALID_EMAIL") || error_msg.contains("WEAK_PASSWORD") {
info.email_auth_enabled = Some(true);
TestResult::ok(
status,
"Email/password auth is ENABLED (validation active)",
)
} else if error_msg.contains("EMAIL_EXISTS") {
info.email_auth_enabled = Some(true);
TestResult::ok(
status,
"Email/password auth ENABLED (email collision check works)",
)
} else {
TestResult::fail(Some(status), if error_msg.is_empty() { "Unknown" } else { &error_msg }, None)
}
}
pub fn test_fetch_providers(
client: &Client,
key: &str,
info: &mut FirebaseProjectInfo,
) -> TestResult {
let url = format!(
"https://identitytoolkit.googleapis.com/v1/accounts:createAuthUri?key={}",
key
);
let body = serde_json::json!({
"identifier": "test@gmail.com",
"continueUri": "http://localhost"
});
let resp = match client.post(&url).json(&body).send() {
Ok(r) => r,
Err(e) => return TestResult::fail(None, e.to_string(), None),
};
let status = resp.status().as_u16();
let data: Value = resp.json().unwrap_or_default();
if status == 200 {
let mut all_providers: Vec<String> = Vec::new();
if let Some(arr) = data.get("allProviders").and_then(|v| v.as_array()) {
for p in arr {
if let Some(s) = p.as_str() {
all_providers.push(s.to_string());
}
}
}
if let Some(arr) = data.get("signinMethods").and_then(|v| v.as_array()) {
for p in arr {
if let Some(s) = p.as_str() {
if !all_providers.contains(&s.to_string()) {
all_providers.push(s.to_string());
}
}
}
}
info.enabled_providers.extend(all_providers.clone());
let detail = if all_providers.is_empty() {
"Providers: None configured".to_string()
} else {
format!("Providers: {}", all_providers.join(", "))
};
let mut result = TestResult::ok(status, detail);
result = result.with_extra("providers", all_providers.join(","));
result
} else {
let msg = extract_error_message(&data);
TestResult::fail(Some(status), msg, None)
}
}
pub fn test_identity_toolkit_lookup(
client: &Client,
key: &str,
_info: &mut FirebaseProjectInfo,
) -> TestResult {
let url = format!(
"https://identitytoolkit.googleapis.com/v1/accounts:lookup?key={}",
key
);
let body = serde_json::json!({"idToken": "invalid_test_token"});
let resp = match client.post(&url).json(&body).send() {
Ok(r) => r,
Err(e) => return TestResult::fail(None, e.to_string(), None),
};
let status = resp.status().as_u16();
let data: Value = resp.json().unwrap_or_default();
let error_msg = extract_error_message(&data);
if status == 400 && error_msg.contains("INVALID_ID_TOKEN") {
TestResult::ok(
status,
"Identity Toolkit accessible (works with valid token)",
)
} else if status == 401 || status == 403 {
TestResult::fail(
Some(status),
&error_msg,
Some("API key not authorized for Identity Toolkit".into()),
)
} else {
TestResult::fail(Some(status), error_msg, None)
}
}
pub fn test_secure_token(
client: &Client,
key: &str,
_info: &mut FirebaseProjectInfo,
) -> TestResult {
let url = format!("https://securetoken.googleapis.com/v1/token?key={}", key);
let params = [
("grantType", "refresh_token"),
("refreshToken", "invalid_test_token"),
];
let resp = match client.post(&url).form(¶ms).send() {
Ok(r) => r,
Err(e) => return TestResult::fail(None, e.to_string(), None),
};
let status = resp.status().as_u16();
let data: Value = resp.json().unwrap_or_default();
let error_msg = extract_error_message(&data);
if status == 400 {
if error_msg.contains("INVALID_REFRESH_TOKEN") {
return TestResult::ok(
status,
"SecureToken API accessible (refresh would work with valid token)",
);
} else if error_msg.contains("API key not valid") {
return TestResult::fail(
Some(status),
"Invalid API key",
Some("Key not valid for SecureToken API".into()),
);
}
}
if status == 401 || status == 403 {
TestResult::fail(Some(status), &error_msg, None)
} else {
TestResult {
success: status != 401 && status != 403,
status_code: Some(status),
error: if status == 401 || status == 403 {
Some(error_msg.clone())
} else {
None
},
detail: if !error_msg.is_empty() {
Some(format!("Response: {}", error_msg))
} else {
None
},
extra: Default::default(),
}
}
}
pub fn test_password_reset(
client: &Client,
key: &str,
_info: &mut FirebaseProjectInfo,
) -> TestResult {
let url = format!(
"https://identitytoolkit.googleapis.com/v1/accounts:sendOobCode?key={}",
key
);
let body = serde_json::json!({
"requestType": "PASSWORD_RESET",
"email": "nonexistent_test_12345@example.com"
});
let resp = match client.post(&url).json(&body).send() {
Ok(r) => r,
Err(e) => return TestResult::fail(None, e.to_string(), None),
};
let status = resp.status().as_u16();
let data: Value = resp.json().unwrap_or_default();
let error_msg = extract_error_message(&data);
if status == 200 {
TestResult::ok(
status,
"[WARNING] Password reset emails can be sent — potential spam vector",
)
} else if error_msg.contains("EMAIL_NOT_FOUND") {
TestResult::ok(
status,
"[INFO] Email enumeration possible via PASSWORD_RESET",
)
} else if status == 401 || status == 403 {
TestResult::fail(
Some(status),
&error_msg,
Some("Password reset endpoint restricted".into()),
)
} else {
TestResult::fail(Some(status), error_msg, None)
}
}
fn extract_error_message(data: &Value) -> String {
if let Some(err) = data.get("error") {
if let Some(msg) = err.get("message").and_then(|m| m.as_str()) {
return msg.to_string();
}
return err.to_string();
}
String::new()
}