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::{FirebaseConfig, FirebaseProjectInfo, TestResult};
use crate::firebase::installations::generate_fid;

/// Test Firebase Cloud Messaging legacy HTTP API — emulates sendMessage().
pub fn test_fcm_send(
    client: &Client,
    key: &str,
    info: &mut FirebaseProjectInfo,
) -> TestResult {
    let url = "https://fcm.googleapis.com/fcm/send";
    let body = serde_json::json!({
        "to": "/topics/__test_nonexistent__",
        "data": {"test": "ping"}
    });

    let resp = match client
        .post(url)
        .header("Authorization", format!("key={}", key))
        .json(&body)
        .send()
    {
        Ok(r) => r,
        Err(e) => return TestResult::fail(None, e.to_string(), None),
    };

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

    if status == 200 {
        info.fcm_enabled = Some(true);
        let data: Value = resp.json().unwrap_or_default();
        let mut result =
            TestResult::ok(status, "FCM Legacy API ENABLED — can send push notifications");
        if let Some(mid) = data.get("message_id").and_then(|v| v.as_str()) {
            result = result.with_extra("message_id", mid);
        }
        result
    } else if status == 401 {
        info.fcm_enabled = Some(false);
        TestResult::fail(
            Some(status),
            "Unauthorized",
            Some("FCM not authorized for this key (may need server key)".into()),
        )
    } else {
        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)
    }
}

/// Emulate FCM getToken() — register for a push notification token.
pub fn test_fcm_token_registration(
    client: &Client,
    config: &FirebaseConfig,
    info: &mut FirebaseProjectInfo,
) -> TestResult {
    let sender_id = match &config.gcm_sender_id {
        Some(id) => id.clone(),
        None => {
            return TestResult::fail(
                None,
                "Missing gcm_sender_id",
                Some("FCM token registration requires sender ID".into()),
            )
        }
    };

    let url = "https://fcmtoken.googleapis.com/register";
    let fid = generate_fid();
    let app_id = &fid[..11.min(fid.len())];

    let params = [
        ("app", "com.flintbase.test"),
        ("sender", &sender_id),
        ("device", "0"),
        ("app_ver", "1.0.0"),
        ("gcm_ver", "23.4.0"),
        ("X-appid", app_id),
        ("X-scope", "*"),
    ];

    let resp = match client
        .post(url)
        .header("Authorization", format!("AidLogin {}:0", sender_id))
        .form(&params)
        .send()
    {
        Ok(r) => r,
        Err(e) => return TestResult::fail(None, e.to_string(), None),
    };

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

    if status == 200 {
        if text.starts_with("token=") {
            let token = text.splitn(2, '=').nth(1).unwrap_or(&text);
            info.fcm_token_obtained = Some(true);
            let preview = if token.len() > 50 {
                format!("{}...", &token[..50])
            } else {
                token.to_string()
            };
            TestResult::ok(status, "FCM token registration endpoint accessible")
                .with_extra("token_preview", preview)
        } else if text.contains("Error") {
            TestResult::fail(
                Some(status),
                &text,
                Some("FCM registration returned error".into()),
            )
        } else {
            TestResult::fail(
                Some(status),
                format!("HTTP {}", status),
                Some(text.chars().take(100).collect()),
            )
        }
    } else {
        TestResult::fail(
            Some(status),
            format!("HTTP {}", status),
            Some(text.chars().take(100).collect()),
        )
    }
}

/// Test FCM v1 API (HTTP v1) — requires OAuth but tests endpoint accessibility.
pub fn test_fcm_v1_api(
    client: &Client,
    config: &FirebaseConfig,
    _info: &mut FirebaseProjectInfo,
) -> TestResult {
    let project_id = match &config.project_id {
        Some(id) => id.clone(),
        None => {
            return TestResult::fail(
                None,
                "No project_id",
                Some("FCM v1 requires project ID".into()),
            )
        }
    };

    let url = format!(
        "https://fcm.googleapis.com/v1/projects/{}/messages:send",
        project_id
    );
    let body = serde_json::json!({
        "message": {
            "topic": "__test_nonexistent__",
            "data": {"test": "ping"}
        }
    });

    let resp = match client
        .post(&url)
        .header("Authorization", "Bearer invalid_test_token")
        .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 == 401 {
        let error_str = data.get("error").map(|e| e.to_string()).unwrap_or_default();
        if error_str.contains("OAuth") || error_str.to_lowercase().contains("authentication") {
            return TestResult::ok(
                status,
                "FCM v1 API exists — requires OAuth (expected)",
            );
        }
    } else if status == 404 {
        return TestResult::fail(
            Some(status),
            "Project not found",
            Some("FCM v1 API: project doesn't exist or FCM not enabled".into()),
        );
    }

    let msg = data
        .get("error")
        .and_then(|e| e.get("message"))
        .and_then(|m| m.as_str())
        .unwrap_or("")
        .to_string();
    TestResult::fail(
        Some(status),
        if msg.is_empty() {
            format!("{}", status)
        } else {
            msg
        },
        None,
    )
}

/// Test FCM topic subscription API — emulates subscribeToTopic().
pub fn test_fcm_topic_management(
    client: &Client,
    key: &str,
    info: &mut FirebaseProjectInfo,
) -> TestResult {
    let url = "https://iid.googleapis.com/iid/v1:batchAdd";
    let body = serde_json::json!({
        "to": "/topics/test_topic",
        "registration_tokens": ["invalid_test_token"]
    });

    let resp = match client
        .post(url)
        .header("Authorization", format!("key={}", key))
        .json(&body)
        .send()
    {
        Ok(r) => r,
        Err(e) => return TestResult::fail(None, e.to_string(), None),
    };

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

    if status == 200 {
        info.fcm_topics_accessible = Some(true);
        TestResult::ok(status, "FCM topic management API accessible with this key")
    } else if status == 401 {
        TestResult::fail(
            Some(status),
            "Unauthorized",
            Some("API key not authorized for topic management (may need server key)".into()),
        )
    } else {
        let data: Value = resp.json().unwrap_or_default();
        let msg = data
            .get("error")
            .and_then(|e| e.as_str())
            .unwrap_or("")
            .to_string();
        TestResult::fail(
            Some(status),
            if msg.is_empty() {
                format!("HTTP {}", status)
            } else {
                msg
            },
            None,
        )
    }
}