Skip to main content

admin/
nonce_auth.rs

1//! Nonce-auth integration module
2//!
3//! 提供 nonce-auth 与 gRPC protobuf 之间的转换和封装
4
5use crate::error::{AdminError, Result};
6use ::nonce_auth::{CredentialBuilder, NonceCredential};
7use actrix_proto::NonceCredential as ProtoCredential;
8
9/// 将 nonce-auth 凭证转换为 protobuf 格式
10///
11/// # Arguments
12///
13/// * `credential` - nonce-auth 生成的凭证
14///
15/// # Returns
16///
17/// protobuf 格式的凭证,可直接嵌入 gRPC 消息
18pub fn to_proto_credential(credential: NonceCredential) -> ProtoCredential {
19    ProtoCredential {
20        timestamp: credential.timestamp,
21        nonce: credential.nonce,
22        signature: credential.signature,
23    }
24}
25
26/// 将 protobuf 凭证转换为 nonce-auth 格式
27///
28/// # Arguments
29///
30/// * `proto` - protobuf 格式的凭证
31///
32/// # Returns
33///
34/// nonce-auth 格式的凭证,用于服务端验证
35pub fn from_proto_credential(proto: &ProtoCredential) -> NonceCredential {
36    NonceCredential {
37        timestamp: proto.timestamp,
38        nonce: proto.nonce.clone(),
39        signature: proto.signature.clone(),
40    }
41}
42
43/// 为请求生成认证凭证
44///
45/// # Arguments
46///
47/// * `shared_secret` - 共享密钥(hex 解码后的字节)
48/// * `payload` - 请求负载(用于签名)
49///
50/// # Returns
51///
52/// protobuf 格式的认证凭证
53///
54/// # Example
55///
56/// ```ignore
57/// let secret = hex::decode("abc123...").unwrap();
58/// let payload = format!("health_check:{}", node_id);
59/// let credential = generate_credential(&secret, payload.as_bytes())?;
60/// ```
61pub fn generate_credential(shared_secret: &[u8], payload: &[u8]) -> Result<ProtoCredential> {
62    let credential = CredentialBuilder::new(shared_secret)
63        .sign(payload)
64        .map_err(|e| AdminError::Authentication(format!("Failed to generate credential: {e}")))?;
65
66    Ok(to_proto_credential(credential))
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_credential_conversion() {
75        let original = NonceCredential {
76            timestamp: 1699999999,
77            nonce: "550e8400-e29b-41d4-a716-446655440000".to_string(),
78            signature: "YWJjMTIz".to_string(),
79        };
80
81        let proto = to_proto_credential(original.clone());
82        let converted = from_proto_credential(&proto);
83
84        assert_eq!(original.timestamp, converted.timestamp);
85        assert_eq!(original.nonce, converted.nonce);
86        assert_eq!(original.signature, converted.signature);
87    }
88
89    #[test]
90    fn test_generate_credential() {
91        let secret = b"test-shared-secret-key-32-bytes!";
92        let payload = b"health_check:test-node";
93
94        let result = generate_credential(secret, payload);
95        assert!(result.is_ok());
96
97        let credential = result.unwrap();
98        assert!(credential.timestamp > 0);
99        assert!(!credential.nonce.is_empty());
100        assert!(!credential.signature.is_empty());
101    }
102
103    #[test]
104    fn test_generate_credential_different_payloads() {
105        let secret = b"test-shared-secret-key-32-bytes!";
106        let payload1 = b"health_check:node1";
107        let payload2 = b"health_check:node2";
108
109        let cred1 = generate_credential(secret, payload1).unwrap();
110        let cred2 = generate_credential(secret, payload2).unwrap();
111
112        // 不同的 payload 应该产生不同的签名
113        assert_ne!(cred1.signature, cred2.signature);
114        // 但 nonce 应该都是唯一的
115        assert_ne!(cred1.nonce, cred2.nonce);
116    }
117}