Fork development of atproto-crates. I contributed atproto-oauth-dioxus
rust atproto
0

Configure Feed

Select the types of activity you want to include in your feed.

feat(atproto-oauth-dioxus): add tracing logging, zeroize support, and cleanup deps

- Add tracing::error!() calls at all error sites in server.rs
- Add optional zeroize feature with ZeroizeOnDrop on ActiveSession
- Fix move-out-of-Drop errors when zeroize feature is enabled
- Add workspace dependency entry for atproto-oauth-dioxus
- Remove redundant serde features (already in workspace definition)

Sam Zanca (Jul 7, 2026, 1:21 PM EDT) 72d508f0 a3876b74

+41 -5
+2
Cargo.lock
··· 500 500 "serde_json", 501 501 "thiserror 2.0.18", 502 502 "tokio", 503 + "tracing", 503 504 "url", 504 505 "web-sys", 506 + "zeroize", 505 507 ] 506 508 507 509 [[package]]
+1
Cargo.toml
··· 46 46 atproto-oauth = { version = "0.15.0-alpha.2", path = "crates/atproto-oauth" } 47 47 atproto-oauth-aip = { version = "0.15.0-alpha.2", path = "crates/atproto-oauth-aip" } 48 48 atproto-oauth-axum = { version = "0.15.0-alpha.2", path = "crates/atproto-oauth-axum" } 49 + atproto-oauth-dioxus = { version = "0.15.0-alpha.2", path = "crates/atproto-oauth-dioxus" } 49 50 atproto-pds = { version = "0.15.0-alpha.2", path = "crates/atproto-pds" } 50 51 atproto-record = { version = "0.15.0-alpha.2", path = "crates/atproto-record" } 51 52 atproto-repo = { version = "0.15.0-alpha.2", path = "crates/atproto-repo" }
+9 -1
crates/atproto-oauth-dioxus/Cargo.toml
··· 17 17 [dependencies] 18 18 dioxus = { version = "0.7.1", features = ["router", "fullstack"] } 19 19 20 - serde = { workspace = true, features = ["derive"] } 20 + serde.workspace = true 21 21 serde_json.workspace = true 22 22 thiserror.workspace = true 23 + tracing.workspace = true 23 24 24 25 url = "2.5" 25 26 web-sys = { version = "0.3", features = ["Window", "Location", "Storage"] } 27 + 28 + zeroize = { workspace = true, optional = true } 26 29 27 30 atproto-identity = { workspace = true, optional = true } 28 31 atproto-oauth = { workspace = true, optional = true } ··· 51 54 hickory-dns = [ 52 55 "atproto-identity?/hickory-dns", 53 56 "atproto-oauth?/hickory-dns", 57 + ] 58 + zeroize = [ 59 + "dep:zeroize", 60 + "atproto-identity?/zeroize", 61 + "atproto-oauth?/zeroize", 54 62 ] 55 63 56 64 [lints]
+29 -4
crates/atproto-oauth-dioxus/src/server.rs
··· 9 9 }; 10 10 use p256::SecretKey; 11 11 12 + #[cfg(feature = "zeroize")] 13 + use zeroize::{Zeroize, ZeroizeOnDrop}; 14 + 12 15 use crate::errors::DioxusOAuthError; 13 16 use crate::types::SessionData; 14 17 ··· 41 44 /// can retrieve this session to make DPoP-authenticated API calls to the 42 45 /// user's PDS on their behalf. 43 46 #[derive(Clone)] 47 + #[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))] 44 48 #[allow(dead_code)] 45 49 pub struct ActiveSession { 46 50 /// The user's decentralized identifier (DID). ··· 73 77 let trimmed = seed_hex.trim(); 74 78 if !trimmed.is_empty() { 75 79 let seed = hex::decode(trimmed) 80 + .inspect_err(|e| tracing::error!(error = ?e, "Invalid OAUTH_KEY_SEED hex")) 76 81 .map_err(|e| DioxusOAuthError::InvalidKeySeed(format!("Invalid hex: {}", e)))?; 77 82 let seed: [u8; 32] = seed.try_into().map_err(|_| { 83 + tracing::error!("OAUTH_KEY_SEED must be exactly 32 bytes (64 hex chars)"); 78 84 DioxusOAuthError::InvalidKeySeed( 79 85 "OAUTH_KEY_SEED must be exactly 32 bytes (64 hex chars)".to_string(), 80 86 ) 81 87 })?; 82 88 let sk = SecretKey::from_slice(&seed).map_err(|_| { 89 + tracing::error!("OAUTH_KEY_SEED is not a valid P-256 private key"); 83 90 DioxusOAuthError::InvalidKeySeed( 84 91 "OAUTH_KEY_SEED is not a valid P-256 private key".to_string(), 85 92 ) ··· 88 95 } 89 96 } 90 97 generate_key(KeyType::P256Private) 98 + .inspect_err(|e| tracing::error!(error = ?e, "Failed to generate OAuth signing key")) 91 99 .map_err(|e| DioxusOAuthError::KeyInitializationFailed(e.to_string())) 92 100 } 93 101 94 102 /// Derives the public key JWKS for the signing key. 95 103 pub fn signing_key_jwks() -> Result<serde_json::Value, DioxusOAuthError> { 96 104 let public_key = to_public(&SIGNING_KEY) 105 + .inspect_err(|e| tracing::error!(error = ?e, "Failed to derive public key from signing key")) 97 106 .map_err(|e| DioxusOAuthError::PublicKeyDerivationFailed(e.to_string()))?; 98 107 let jwk = atproto_oauth::jwk::generate(&public_key) 108 + .inspect_err(|e| tracing::error!(error = ?e, "Failed to generate JWK")) 99 109 .map_err(|e| DioxusOAuthError::JwkGenerationFailed(e.to_string()))?; 100 110 let jwks = atproto_oauth::jwk::WrappedJsonWebKeySet { keys: vec![jwk] }; 101 - serde_json::to_value(jwks).map_err(|e| DioxusOAuthError::JwkGenerationFailed(e.to_string())) 111 + serde_json::to_value(jwks) 112 + .inspect_err(|e| tracing::error!(error = ?e, "Failed to serialize JWKS")) 113 + .map_err(|e| DioxusOAuthError::JwkGenerationFailed(e.to_string())) 102 114 } 103 115 104 116 fn generate_random_hex(len: usize) -> String { ··· 140 152 let doc = identity_resolver 141 153 .resolve(&handle) 142 154 .await 155 + .inspect_err(|e| tracing::error!(error = ?e, "Failed to resolve handle: {}", &handle)) 143 156 .map_err(|e| DioxusOAuthError::HandleResolutionFailed(e.to_string()))?; 144 157 145 158 let pds_url = doc 146 159 .pds_endpoints() 147 160 .first() 148 161 .ok_or_else(|| { 162 + tracing::error!("No PDS endpoints in DID document for handle: {}", &handle); 149 163 DioxusOAuthError::PdsResolutionFailed("No PDS endpoints in DID document".to_string()) 150 164 })? 151 165 .to_string(); 152 166 153 167 let (_protected, auth_server) = pds_resources(&http_client, &pds_url) 154 168 .await 169 + .inspect_err(|e| tracing::error!(error = ?e, "Failed to discover PDS resources at: {}", &pds_url)) 155 170 .map_err(|e| DioxusOAuthError::PdsResourceDiscoveryFailed(e.to_string()))?; 156 171 157 172 let base = base_url(); ··· 173 188 174 189 let signing_key = get_signing_key().clone(); 175 190 let dpop_key = generate_key(KeyType::P256Private) 191 + .inspect_err(|e| tracing::error!(error = ?e, "Failed to generate DPoP key")) 176 192 .map_err(|e| DioxusOAuthError::KeyInitializationFailed(e.to_string()))?; 177 193 178 194 let oauth_client = OAuthClient { ··· 197 213 &oauth_request_state, 198 214 ) 199 215 .await 216 + .inspect_err(|e| tracing::error!(error = ?e, "OAuth authorization initiation failed for handle: {}", &handle)) 200 217 .map_err(|e| DioxusOAuthError::OAuthInitFailed(e.to_string()))?; 201 218 202 219 let oauth_request = OAuthRequest { ··· 245 262 let mut states = OAUTH_STATES.lock().await; 246 263 states 247 264 .remove(&state) 248 - .ok_or(DioxusOAuthError::InvalidOAuthState)? 265 + .ok_or_else(|| { 266 + tracing::error!("Invalid or expired OAuth state during callback"); 267 + DioxusOAuthError::InvalidOAuthState 268 + })? 249 269 }; 250 270 251 271 let http_client = reqwest::Client::new(); ··· 265 285 &stored.auth_server, 266 286 ) 267 287 .await 288 + .inspect_err(|e| tracing::error!(error = ?e, "Token exchange failed")) 268 289 .map_err(|e| DioxusOAuthError::TokenExchangeFailed(e.to_string()))?; 269 290 270 291 let did = token_response 271 292 .sub 272 - .ok_or(DioxusOAuthError::MissingSubField)?; 293 + .clone() 294 + .ok_or_else(|| { 295 + tracing::error!("Token response missing 'sub' (DID) field"); 296 + DioxusOAuthError::MissingSubField 297 + })?; 273 298 274 299 ACTIVE_SESSIONS.lock().await.insert( 275 300 did.clone(), ··· 286 311 did, 287 312 handle: stored.handle, 288 313 pds_endpoint: stored.pds_url, 289 - access_token: token_response.access_token, 314 + access_token: token_response.access_token.clone(), 290 315 }) 291 316 }