flintbase 0.3.1

Google / Firebase API key analyzer and APK secret scanner — tests keys against 20+ endpoints and extracts hardcoded credentials from Android apps
use reqwest::blocking::Client;
use serde_json::Value;

use crate::config::{FirebaseProjectInfo, TestResult};

/// Test Realtime Database public access.
pub fn test_realtime_db(
    client: &Client,
    _key: &str,
    project_id: Option<&str>,
    info: &mut FirebaseProjectInfo,
) -> TestResult {
    let pid = match project_id {
        Some(p) => p,
        None => {
            return TestResult::fail(
                None,
                "No project ID available",
                Some("Provide --project-id to test Realtime Database".into()),
            )
        }
    };

    let url = format!(
        "https://{}-default-rtdb.firebaseio.com/.json?shallow=true",
        pid
    );

    let resp = match client.get(&url).send() {
        Ok(r) => r,
        Err(e) => return TestResult::fail(None, e.to_string(), None),
    };

    let status = resp.status().as_u16();

    match status {
        200 => {
            let data: Value = resp.json().unwrap_or_default();
            info.realtime_db_public_read = Some(true);
            if data.is_null() || (data.is_object() && data.as_object().unwrap().is_empty()) {
                TestResult::ok(status, "Database publicly readable but empty")
            } else {
                let preview = {
                    let s = data.to_string();
                    if s.len() > 200 {
                        format!("{}...", &s[..200])
                    } else {
                        s
                    }
                };
                TestResult::ok(status, "[CRITICAL] Database is PUBLICLY READABLE!")
                    .with_extra("data_preview", preview)
            }
        }
        401 => {
            info.realtime_db_public_read = Some(false);
            TestResult::fail(
                Some(status),
                "Permission denied",
                Some("Database requires authentication (properly secured)".into()),
            )
        }
        404 => TestResult::fail(
            Some(status),
            "Not found",
            Some("Realtime Database not enabled or different URL".into()),
        ),
        _ => {
            let text = resp.text().unwrap_or_default();
            let detail = if text.is_empty() {
                None
            } else {
                Some(text.chars().take(100).collect())
            };
            TestResult::fail(Some(status), format!("HTTP {}", status), detail)
        }
    }
}

/// Test Firebase Storage bucket access.
pub fn test_firebase_storage(
    client: &Client,
    _key: &str,
    project_id: Option<&str>,
    info: &mut FirebaseProjectInfo,
) -> TestResult {
    let pid = match project_id {
        Some(p) => p,
        None => {
            return TestResult::fail(
                None,
                "No project_id",
                Some("Provide --project-id to test Storage".into()),
            )
        }
    };

    let bucket = format!("{}.appspot.com", pid);
    info.storage_bucket = Some(bucket.clone());

    let url = format!(
        "https://firebasestorage.googleapis.com/v0/b/{}/o",
        bucket
    );

    let resp = match client.get(&url).send() {
        Ok(r) => r,
        Err(e) => return TestResult::fail(None, e.to_string(), None),
    };

    let status = resp.status().as_u16();

    match status {
        200 => {
            let data: Value = resp.json().unwrap_or_default();
            let items = data
                .get("items")
                .and_then(|v| v.as_array())
                .cloned()
                .unwrap_or_default();
            info.storage_public = Some(true);

            let item_names: Vec<String> = items
                .iter()
                .take(10)
                .filter_map(|i| i.get("name").and_then(|n| n.as_str()).map(String::from))
                .collect();

            let mut result = TestResult::ok(
                status,
                format!(
                    "[CRITICAL] Storage bucket PUBLICLY LISTABLE! ({} items)",
                    items.len()
                ),
            );
            if !item_names.is_empty() {
                result = result.with_extra("items", item_names.join(", "));
            }
            result
        }
        403 => {
            info.storage_public = Some(false);
            TestResult::fail(
                Some(status),
                "Access denied",
                Some("Storage bucket properly secured".into()),
            )
        }
        _ => TestResult::fail(Some(status), format!("HTTP {}", status), None),
    }
}