signer-daemon 0.3.2

Signer daemon package.
Documentation
use crate::DaemonResult as Result;
use serde::{Deserialize, Serialize};
use signer_core::{SignerJWT, SignerJWTClaims, SignerJWTHeader, SignerUser};

// 移除未使用的导入

// 定义 AuthCodeDetail 和 AuthCodeStatus,与 signer-hub 保持一致
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthCodeStatus {
    Pending,
    Fetched,
    Expired,
    Completed,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AuthCodeDetail {
    pub code: String,
    pub state: String,
    pub status: AuthCodeStatus,
    pub expire_at: i64,
    pub jwt: Option<String>,
}

#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct PostAuthRequest {
    #[serde(rename = "jwt")]
    pub jwt: String,
    #[serde(rename = "state")]
    pub state: String,
}

impl PostAuthRequest {
    pub fn new(jwt: String, state: String) -> PostAuthRequest {
        PostAuthRequest { jwt, state }
    }
}

pub struct SignerRemoteAuth {
    pub base_url: String,
}

impl SignerRemoteAuth {
    pub fn from_url(url: String) -> Self {
        Self { base_url: url }
    }

    /// 获取认证详情,包含 state 等信息
    pub async fn get_auth_detail(&self, target: &str) -> Result<AuthCodeDetail> {
        let client = reqwest::Client::new();

        let response = client.get(target).send().await?;

        if !response.status().is_success() {
            return Err(crate::DaemonError::Signer(crate::SignerError::Msg(
                format!("获取认证详情失败: {}", response.status()),
            )));
        }

        let detail: AuthCodeDetail = response.json().await?;
        Ok(detail)
    }

    pub async fn post_auth(
        &self,
        user: &SignerUser,
        target: String,
        state: String,
        expire: i64,
    ) -> Result<()> {
        let claims =
            SignerJWTClaims::default(&user, target.clone(), uuid::Uuid::new_v4().to_string())
                .with_expired_duration(chrono::Duration::milliseconds(expire));

        let jwt = SignerJWT::new(SignerJWTHeader::default(&user), claims)
            .encode(&user)
            .expect("encode jwt string failed");

        let request = PostAuthRequest::new(jwt, state);

        // 使用原生 reqwest 发送请求到 target 地址
        let client = reqwest::Client::new();

        let response = client.post(&target).json(&request).send().await?;

        if !response.status().is_success() {
            return Err(crate::DaemonError::Signer(crate::SignerError::Msg(
                format!("认证请求失败: {}", response.status()),
            )));
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use futures_util::StreamExt;
    use signer_core::SignerUser;
    use std::time::Duration;
    use tokio::sync::mpsc;

    #[tokio::test]
    async fn test_e2e_auth_flow() {
        // 这里是 E2E 测试的起点
        // 我们需要一个正在运行的 signer-hub 实例
        let base_url = "http://localhost:8080";

        let (tx, mut rx) = mpsc::channel::<String>(1); // 只传递 code

        // 协程一:客户端,通过 SSE 获取 code
        let client_handle = tokio::spawn(async move {
            let client = reqwest::Client::new();
            let mut stream = client
                .get(format!("{}/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);

                // 处理完整的 SSE 事件
                while let Some(event_end) = buffer.find("\n\n") {
                    let event = buffer[..event_end].to_string();
                    buffer = buffer[event_end + 2..].to_string();

                    println!("Received event: {}", event);

                    // 解析 SSE 事件
                    for line in event.lines() {
                        if line.starts_with("data:") {
                            let data = line.trim_start_matches("data:").trim();

                            // 尝试解析为 JSON
                            match serde_json::from_str::<serde_json::Value>(data) {
                                Ok(auth_code) => {
                                    if !code_received {
                                        // 第一次收到的是包含 code 的 AuthCode(status 为 Pending)
                                        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" {
                                                println!(
                                                    "Received code: {}, status: {}",
                                                    code, status
                                                );
                                                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(jwt) =
                                                    auth_code.get("jwt").and_then(|j| j.as_str())
                                                {
                                                    println!(
                                                        "Received JWT: {}, status: {}",
                                                        jwt, status
                                                    );
                                                    assert!(!jwt.is_empty());
                                                    return;
                                                }
                                            }
                                        }
                                    }
                                }
                                Err(_) => {
                                    // JSON 解析失败,忽略这个事件
                                }
                            }
                        }
                    }
                }
            }
        });

        // 协程二:认证处理器,接收 code,通过 get_auth_detail 方法获取 state,发送 post_auth 请求
        let auth_handle = tokio::spawn(async move {
            if let Some(code) = rx.recv().await {
                // 使用 SignerRemoteAuth 的 get_auth_detail 方法获取认证详情
                let auth = SignerRemoteAuth::from_url(base_url.to_string());
                // 构造完整的认证详情获取URL
                let detail_url = format!("{}/api/auth/{}", base_url, code);
                let detail = auth.get_auth_detail(&detail_url).await.unwrap();

                println!("Retrieved state from detail API: {}", detail.state);
                println!(
                    "Auth detail: code={}, status={:?}, expire_at={}",
                    detail.code, detail.status, detail.expire_at
                );

                let user = SignerUser::generete("tester").unwrap();
                let target_url = format!("{}/api/auth/{}", base_url, code);
                let result = auth.post_auth(&user, target_url, detail.state, 3600).await;
                assert!(result.is_ok());
            }
        });

        // 等待两个协程完成,或者超时
        let timeout = Duration::from_secs(10);
        let _ = tokio::time::timeout(timeout, async {
            let (client_res, auth_res) = tokio::join!(client_handle, auth_handle);
            client_res.unwrap();
            auth_res.unwrap();
        })
        .await
        .expect("测试超时!");
    }
}