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
pub mod auth;
pub mod fcm;
pub mod installations;
pub mod storage;

use indexmap::IndexMap;

use indicatif::{ProgressBar, ProgressStyle};
use reqwest::blocking::Client;

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

/// Run all Firebase-specific tests and return ordered results + project info.
pub fn run_firebase_deep_tests(
    client: &Client,
    config: &FirebaseConfig,
) -> (IndexMap<String, TestResult>, FirebaseProjectInfo) {
    let mut info = FirebaseProjectInfo {
        project_id: config.project_id.clone(),
        project_number: config.gcm_sender_id.clone(),
        ..Default::default()
    };
    let mut results = IndexMap::new();

    // ── Core Firebase Auth tests ─────────────────────────────────────────────
    println!("\n\x1b[1;34mRunning Firebase Deep Analysis...\x1b[0m");

    let auth_tests: Vec<(&str, Box<dyn FnOnce(&mut FirebaseProjectInfo) -> TestResult>)> = vec![
        (
            "Firebase: Anonymous Auth",
            Box::new(|i| auth::test_anonymous_signup(client, &config.api_key, i)),
        ),
        (
            "Firebase: Email/Password Auth",
            Box::new(|i| auth::test_email_signup_capability(client, &config.api_key, i)),
        ),
        (
            "Firebase: Provider Enumeration",
            Box::new(|i| auth::test_fetch_providers(client, &config.api_key, i)),
        ),
        (
            "Firebase: Identity Toolkit",
            Box::new(|i| auth::test_identity_toolkit_lookup(client, &config.api_key, i)),
        ),
        (
            "Firebase: SecureToken API",
            Box::new(|i| auth::test_secure_token(client, &config.api_key, i)),
        ),
        (
            "Firebase: Password Reset",
            Box::new(|i| auth::test_password_reset(client, &config.api_key, i)),
        ),
    ];

    let pb = make_progress_bar(auth_tests.len() as u64, "Testing Firebase Auth...");
    for (name, test_fn) in auth_tests {
        let result = test_fn(&mut info);
        results.insert(name.to_string(), result);
        pb.inc(1);
    }
    pb.finish_and_clear();

    // ── FCM tests ────────────────────────────────────────────────────────────
    println!("\x1b[2mEmulating app FCM operations...\x1b[0m");

    let fcm_tests: Vec<(&str, Box<dyn FnOnce(&mut FirebaseProjectInfo) -> TestResult>)> = vec![
        (
            "FCM: Legacy Send API",
            Box::new(|i| fcm::test_fcm_send(client, &config.api_key, i)),
        ),
        (
            "FCM: Token Registration",
            Box::new(|i| fcm::test_fcm_token_registration(client, config, i)),
        ),
        (
            "FCM: Topic Management",
            Box::new(|i| fcm::test_fcm_topic_management(client, &config.api_key, i)),
        ),
        (
            "FCM: v1 API (OAuth)",
            Box::new(|i| fcm::test_fcm_v1_api(client, config, i)),
        ),
    ];

    let pb = make_progress_bar(fcm_tests.len() as u64, "Testing FCM services...");
    for (name, test_fn) in fcm_tests {
        let result = test_fn(&mut info);
        results.insert(name.to_string(), result);
        pb.inc(1);
    }
    pb.finish_and_clear();

    // ── Firebase Installations ────────────────────────────────────────────────
    if config.app_id.is_some() {
        println!("\x1b[2mTesting Firebase Installations (device registration)...\x1b[0m");
        results.insert(
            "Firebase: Installations API".to_string(),
            installations::test_firebase_installations(client, config, &mut info),
        );
    }

    // ── Project-specific tests ───────────────────────────────────────────────
    if let Some(ref pid) = config.project_id {
        println!(
            "\x1b[2mTesting project resources: {}\x1b[0m",
            pid
        );
        results.insert(
            "Firebase: Realtime DB".to_string(),
            storage::test_realtime_db(client, &config.api_key, Some(pid), &mut info),
        );
        results.insert(
            "Firebase: Storage Bucket".to_string(),
            storage::test_firebase_storage(client, &config.api_key, Some(pid), &mut info),
        );
    }

    (results, info)
}

fn make_progress_bar(len: u64, msg: &str) -> ProgressBar {
    let pb = ProgressBar::new(len);
    pb.set_style(
        ProgressStyle::with_template("{msg} [{bar:30.cyan/dim}] {pos}/{len}")
            .unwrap()
            .progress_chars("━╸─"),
    );
    pb.set_message(msg.to_string());
    pb
}