posemesh_compute_node/auth/
siwe_after_registration.rs1use super::siwe;
2use super::token_manager::{
3 AccessAuthenticator, SystemClock, TokenManager, TokenManagerConfig, TokenProvider,
4 TokenProviderError,
5};
6use crate::config::NodeConfig;
7use anyhow::{anyhow, Result};
8use async_trait::async_trait;
9use posemesh_node_registration::persist as dds_state;
10use sha3::{Digest, Keccak256};
11use std::sync::Arc;
12use std::time::Duration;
13use tokio::sync::Mutex;
14use tokio::time::sleep;
15use tracing::{info, warn};
16
17type ManagerCell = Arc<Mutex<Option<Arc<SiweTokenManager>>>>;
18type SiweTokenManager = TokenManager<DdsAuthenticator, SystemClock>;
19
20#[derive(Clone)]
21struct DdsAuthenticator {
22 base_url: Arc<String>,
23 priv_hex: Arc<String>,
24 address: Arc<String>,
25}
26
27impl DdsAuthenticator {
28 fn new(base_url: String, priv_hex: String) -> Result<Self> {
29 let address = derive_eth_address(&priv_hex)?;
30 Ok(Self {
31 base_url: Arc::new(base_url),
32 priv_hex: Arc::new(priv_hex),
33 address: Arc::new(address),
34 })
35 }
36}
37
38#[async_trait]
39impl AccessAuthenticator for DdsAuthenticator {
40 async fn login(&self) -> Result<super::siwe::AccessBundle, super::siwe::SiweError> {
41 let meta = siwe::request_nonce(self.base_url.as_str(), self.address.as_str()).await?;
42 let message = siwe::compose_message(&meta, self.address.as_str(), None)?;
43 let signature = siwe::sign_message(self.priv_hex.as_str(), &message)?;
44 siwe::verify(
45 self.base_url.as_str(),
46 self.address.as_str(),
47 &message,
48 &signature,
49 )
50 .await
51 }
52}
53
54pub struct SiweAfterRegistration {
55 authenticator: Arc<DdsAuthenticator>,
56 config: TokenManagerConfig,
57 manager: ManagerCell,
58}
59
60impl SiweAfterRegistration {
61 pub fn from_config(cfg: &NodeConfig) -> Result<Self> {
62 let dds_base_url = cfg
63 .dds_base_url
64 .as_ref()
65 .ok_or_else(|| anyhow!("DDS_BASE_URL required for DDS SIWE authentication"))?
66 .as_str()
67 .to_string();
68
69 let priv_hex = cfg
70 .secp256k1_privhex
71 .as_ref()
72 .filter(|value| !value.trim().is_empty())
73 .ok_or_else(|| anyhow!("SECP256K1_PRIVHEX required for DDS SIWE authentication"))?
74 .to_string();
75
76 let config = TokenManagerConfig {
77 safety_ratio: cfg.token_safety_ratio as f64,
78 max_retries: cfg.token_reauth_max_retries,
79 jitter: Duration::from_millis(cfg.token_reauth_jitter_ms),
80 };
81
82 Self::new(dds_base_url, priv_hex, config)
83 }
84
85 pub fn new(dds_base_url: String, priv_hex: String, config: TokenManagerConfig) -> Result<Self> {
86 let authenticator = Arc::new(DdsAuthenticator::new(dds_base_url, priv_hex)?);
87 Ok(Self {
88 authenticator,
89 config,
90 manager: Arc::new(Mutex::new(None)),
91 })
92 }
93
94 pub async fn start(&self) -> Result<SiweHandle> {
95 let manager = self.ensure_started().await?;
96 Ok(SiweHandle { manager })
97 }
98
99 async fn ensure_started(&self) -> Result<Arc<SiweTokenManager>> {
100 {
101 let guard = self.manager.lock().await;
102 if let Some(existing) = guard.as_ref() {
103 return Ok(existing.clone());
104 }
105 }
106
107 self.wait_for_registration().await?;
108
109 let manager: Arc<SiweTokenManager> = Arc::new(TokenManager::new(
110 Arc::clone(&self.authenticator),
111 Arc::new(SystemClock),
112 self.config.clone(),
113 ));
114 manager.start_bg().await;
115
116 manager
117 .bearer()
118 .await
119 .map_err(|err| anyhow!("initial DDS SIWE login failed: {err}"))?;
120
121 let mut guard = self.manager.lock().await;
122 if let Some(existing) = guard.as_ref() {
123 manager.stop_bg().await;
124 return Ok(existing.clone());
125 }
126 *guard = Some(manager.clone());
127 Ok(manager)
128 }
129
130 async fn wait_for_registration(&self) -> Result<()> {
131 loop {
132 match dds_state::read_node_secret() {
133 Ok(Some(_)) => {
134 info!("DDS registration confirmed; starting SIWE token manager");
135 return Ok(());
136 }
137 Ok(None) => {
138 sleep(Duration::from_secs(1)).await;
139 }
140 Err(err) => {
141 warn!(error = %err, "Failed to read DDS registration status; retrying");
142 sleep(Duration::from_secs(1)).await;
143 }
144 }
145 }
146 }
147}
148
149#[derive(Clone)]
150pub struct SiweHandle {
151 manager: Arc<SiweTokenManager>,
152}
153
154impl SiweHandle {
155 pub async fn bearer(&self) -> Result<String, TokenProviderError> {
156 self.manager.bearer().await
157 }
158
159 pub async fn shutdown(&self) {
160 self.manager.stop_bg().await;
161 }
162}
163
164#[async_trait]
165impl TokenProvider for SiweHandle {
166 async fn bearer(&self) -> crate::auth::token_manager::TokenProviderResult<String> {
167 self.manager.bearer().await
169 }
170
171 async fn on_unauthorized(&self) {
172 self.manager.on_unauthorized_retry().await;
174 }
175}
176
177fn derive_eth_address(priv_hex: &str) -> Result<String> {
178 use k256::{ecdsa::SigningKey, FieldBytes};
179
180 let trimmed = priv_hex.trim_start_matches("0x");
181 let key_bytes =
182 hex::decode(trimmed).map_err(|_| anyhow!("invalid secp256k1 private key hex"))?;
183 if key_bytes.len() != 32 {
184 return Err(anyhow!("secp256k1 private key must be 32 bytes"));
185 }
186 let mut key = [0u8; 32];
187 key.copy_from_slice(&key_bytes);
188 let field_bytes: FieldBytes = key.into();
189 let signing_key = SigningKey::from_bytes(&field_bytes)
190 .map_err(|e| anyhow!("failed to construct signing key: {e}"))?;
191 let verifying_key = signing_key.verifying_key();
192 let encoded = verifying_key.to_encoded_point(false);
193 let pubkey = encoded.as_bytes();
194
195 let mut hasher = Keccak256::new();
196 hasher.update(&pubkey[1..]);
197 let hashed = hasher.finalize();
198 let address_bytes = &hashed[12..];
199 Ok(format!("0x{}", hex::encode(address_bytes)))
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use url::Url;
206
207 fn base_cfg() -> NodeConfig {
208 NodeConfig {
209 dms_base_url: Url::parse("https://dms.example").unwrap(),
210 node_version: "1.0.0".into(),
211 request_timeout_secs: 10,
212 dds_base_url: None,
213 node_url: None,
214 reg_secret: None,
215 secp256k1_privhex: None,
216 heartbeat_jitter_ms: 250,
217 poll_backoff_ms_min: 1000,
218 poll_backoff_ms_max: 30000,
219 token_safety_ratio: 0.75,
220 token_reauth_max_retries: 3,
221 token_reauth_jitter_ms: 500,
222 register_interval_secs: None,
223 register_max_retry: None,
224 max_concurrency: 1,
225 log_format: crate::config::LogFormat::Json,
226 enable_noop: true,
227 noop_sleep_secs: 1,
228 }
229 }
230
231 #[test]
232 fn from_config_errors_when_missing_siwe_fields() {
233 let cfg = base_cfg();
234 assert!(SiweAfterRegistration::from_config(&cfg).is_err());
235 }
236
237 #[test]
238 fn derive_eth_address_matches_expected_value() {
239 let priv_hex = "4c0883a69102937d6231471b5dbb6204fe5129617082798ce3f4fdf2548b6f90";
240 let addr = derive_eth_address(priv_hex).expect("address");
241 assert_eq!(addr, "0xfdbb6caf01414300c16ea14859fec7736d95355f");
242 }
243
244 #[test]
245 fn from_config_produces_instance_when_siwe_configured() {
246 let mut cfg = base_cfg();
247 cfg.dds_base_url = Some(Url::parse("https://dds.example").unwrap());
248 cfg.secp256k1_privhex =
249 Some("4c0883a69102937d6231471b5dbb6204fe5129617082798ce3f4fdf2548b6f90".into());
250
251 assert!(SiweAfterRegistration::from_config(&cfg).is_ok());
252 }
253}