signer-remote 0.4.1

Signer remote communication package.
Documentation
use futures_util::StreamExt;
use signer_core::{SignerKeys, SignerUser};
use signer_remote::auth::SignerRemoteAuth;
use std::time::Duration;
use tokio::sync::mpsc;

#[tokio::test]
async fn test_e2e_auth_flow() {
    let base_url = "http://localhost:8080";

    let (code_tx, mut code_rx) = mpsc::channel::<String>(1);
    let (jwt_tx, mut jwt_rx) = mpsc::channel::<String>(1);

    // Coroutine 1: Client, gets code via SSE
    let client_handle = tokio::spawn(async move {
        let client = reqwest::Client::new();
        let mut stream = client
            .get(format!(/* here */ "{}/api/auth/new", base_url))
            .send()
            .await
            .unwrap()
            .bytes_stream();

        let mut code_received = false;
        let mut buffer = String::new();

        while let Some(item) = stream.next().await {
            let chunk = item.unwrap();
            let chunk_str = String::from_utf8(chunk.to_vec()).unwrap();
            buffer.push_str(&chunk_str);

            while let Some(event_end) = buffer.find("\n\n") {
                let event = buffer[..event_end].to_string();
                buffer = buffer[event_end + 2..].to_string();

                for line in event.lines() {
                    if line.starts_with("data:") {
                        let data = line.trim_start_matches("data:").trim();
                        if let Ok(auth_code) = serde_json::from_str::<serde_json::Value>(data) {
                            if !code_received {
                                if let (Some(code), Some(status)) = (
                                    auth_code.get("code").and_then(|c| c.as_str()),
                                    auth_code.get("status").and_then(|s| s.as_str()),
                                ) {
                                    if status == "Pending" {
                                        code_tx.send(code.to_string()).await.unwrap();
                                        code_received = true;
                                    }
                                }
                            } else {
                                if let Some(status) = auth_code.get("status").and_then(|s| s.as_str()) {
                                    if status == "Completed" {
                                        if let Some(received_jwt) = auth_code.get("jwt").and_then(|j| j.as_str()) {
                                            let expected_jwt = jwt_rx.recv().await.unwrap();
                                            assert_eq!(received_jwt, expected_jwt);
                                            return;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    });

    // Coroutine 2: Auth handler, receives code, gets state, posts auth
    let auth_handle = tokio::spawn(async move {
        if let Some(code) = code_rx.recv().await {
            let auth = SignerRemoteAuth::from_url(base_url.to_string());
            let detail_url = format!(/* here */ "{}/api/auth/{}", base_url, code);
            let detail = auth.get_auth_detail(&detail_url).await.unwrap();

            let keys = SignerKeys::generate().unwrap();
            let user = SignerUser {
                pub_key: keys.pub_key.clone(),
                username: "tester".to_string(),
                ..Default::default()
            };
            
            let claims = signer_auth::SignerJWTClaims::default(&keys, &user, uuid::Uuid::new_v4().to_string(), detail.state.clone())
                .with_expired_duration(chrono::Duration::milliseconds(3600));
            let jwt = signer_auth::SignerJWT::new(signer_auth::SignerJWTHeader::default(&user), claims)
                .encode(&keys)
                .expect("encode jwt string failed");

            jwt_tx.send(jwt.clone()).await.unwrap();

            let target_url = format!(/* here */ "{}/api/auth/{}", base_url, code);
            let result = auth.post_auth(target_url, detail.state, jwt).await;
            assert!(result.is_ok());
        }
    });

    let timeout = Duration::from_secs(10);
    tokio::time::timeout(timeout, async {
        let (client_res, auth_res) = tokio::join!(client_handle, auth_handle);
        client_res.unwrap();
        auth_res.unwrap();
    })
    .await
    .expect("Test timed out!");
}