A better Rust ATProto crate

oauth url nonsense

Changed files
+23 -19
crates
jacquard
src
client
jacquard-oauth
+3 -1
crates/jacquard-oauth/src/client.rs
··· 264 let client_data = ClientSessionData { 265 account_did: token_set.sub.clone(), 266 session_id: auth_req_info.state, 267 - host_url: token_set.iss.clone(), 268 authserver_url: auth_req_info.authserver_url.to_cowstr(), 269 authserver_token_endpoint: auth_req_info.authserver_token_endpoint, 270 authserver_revocation_endpoint: auth_req_info.authserver_revocation_endpoint, ··· 279 }, 280 token_set, 281 }; 282 283 self.create_session(client_data).await 284 }
··· 264 let client_data = ClientSessionData { 265 account_did: token_set.sub.clone(), 266 session_id: auth_req_info.state, 267 + host_url: token_set.aud.clone(), 268 authserver_url: auth_req_info.authserver_url.to_cowstr(), 269 authserver_token_endpoint: auth_req_info.authserver_token_endpoint, 270 authserver_revocation_endpoint: auth_req_info.authserver_revocation_endpoint, ··· 279 }, 280 token_set, 281 }; 282 + 283 + dbg!(&client_data); 284 285 self.create_session(client_data).await 286 }
+1 -2
crates/jacquard-oauth/src/request.rs
··· 528 }; 529 let auth_req_data = AuthRequestData { 530 state, 531 - authserver_url: url::Url::parse(&metadata.server_metadata.issuer) 532 - .expect("Failed to parse issuer URL"), 533 account_did: None, 534 scopes, 535 request_uri: par_response.request_uri.to_cowstr().into_static(),
··· 528 }; 529 let auth_req_data = AuthRequestData { 530 state, 531 + authserver_url: metadata.server_metadata.issuer.clone(), 532 account_did: None, 533 scopes, 534 request_uri: par_response.request_uri.to_cowstr().into_static(),
+4 -4
crates/jacquard-oauth/src/resolver.rs
··· 793 .await 794 .map_err(|e| ResolverError::transport(e))?; 795 if res.status() == StatusCode::OK { 796 - let mut metadata = serde_json::from_slice::<OAuthAuthorizationServerMetadata>(res.body())?; 797 // https://datatracker.ietf.org/doc/html/rfc8414#section-3.3 798 // Accept semantically equivalent issuer (normalize to the requested URL form) 799 if issuer_equivalent(&metadata.issuer, server.as_str()) { 800 - metadata.issuer = server.as_str().into(); 801 Ok(metadata.into_static()) 802 } else { 803 Err(ResolverError::authorization_server_metadata( ··· 827 .await 828 .map_err(|e| ResolverError::transport(e))?; 829 if res.status() == StatusCode::OK { 830 - let mut metadata = serde_json::from_slice::<OAuthProtectedResourceMetadata>(res.body())?; 831 // https://datatracker.ietf.org/doc/html/rfc8414#section-3.3 832 // Accept semantically equivalent resource URL (normalize to the requested URL form) 833 if issuer_equivalent(&metadata.resource, server.as_str()) { 834 - metadata.resource = server.as_str().into(); 835 Ok(metadata.into_static()) 836 } else { 837 Err(ResolverError::authorization_server_metadata(
··· 793 .await 794 .map_err(|e| ResolverError::transport(e))?; 795 if res.status() == StatusCode::OK { 796 + let metadata = serde_json::from_slice::<OAuthAuthorizationServerMetadata>(res.body())?; 797 // https://datatracker.ietf.org/doc/html/rfc8414#section-3.3 798 // Accept semantically equivalent issuer (normalize to the requested URL form) 799 if issuer_equivalent(&metadata.issuer, server.as_str()) { 800 + // if equivalent, keep the canonical form 801 Ok(metadata.into_static()) 802 } else { 803 Err(ResolverError::authorization_server_metadata( ··· 827 .await 828 .map_err(|e| ResolverError::transport(e))?; 829 if res.status() == StatusCode::OK { 830 + let metadata = serde_json::from_slice::<OAuthProtectedResourceMetadata>(res.body())?; 831 // https://datatracker.ietf.org/doc/html/rfc8414#section-3.3 832 // Accept semantically equivalent resource URL (normalize to the requested URL form) 833 if issuer_equivalent(&metadata.resource, server.as_str()) { 834 + // if equivalent, keep the canonical form 835 Ok(metadata.into_static()) 836 } else { 837 Err(ResolverError::authorization_server_metadata(
+2 -3
crates/jacquard-oauth/src/session.rs
··· 22 use serde::{Deserialize, Serialize}; 23 use smol_str::{SmolStr, format_smolstr}; 24 use tokio::sync::Mutex; 25 - use url::Url; 26 27 pub trait DpopDataSource { 28 fn key(&self) -> &Key; ··· 148 pub state: CowStr<'s>, 149 150 // URL of the auth server (eg, PDS or entryway) 151 - pub authserver_url: Url, 152 153 // If the flow started with an account identifier (DID or handle), it should be persisted, to verify against the initial token response. 154 #[serde(skip_serializing_if = "std::option::Option::is_none")] ··· 186 pkce_verifier: self.pkce_verifier.into_static(), 187 dpop_data: self.dpop_data.into_static(), 188 state: self.state.into_static(), 189 - authserver_url: self.authserver_url, 190 account_did: self.account_did.into_static(), 191 scopes: self.scopes.into_static(), 192 }
··· 22 use serde::{Deserialize, Serialize}; 23 use smol_str::{SmolStr, format_smolstr}; 24 use tokio::sync::Mutex; 25 26 pub trait DpopDataSource { 27 fn key(&self) -> &Key; ··· 147 pub state: CowStr<'s>, 148 149 // URL of the auth server (eg, PDS or entryway) 150 + pub authserver_url: CowStr<'s>, 151 152 // If the flow started with an account identifier (DID or handle), it should be persisted, to verify against the initial token response. 153 #[serde(skip_serializing_if = "std::option::Option::is_none")] ··· 185 pkce_verifier: self.pkce_verifier.into_static(), 186 dpop_data: self.dpop_data.into_static(), 187 state: self.state.into_static(), 188 + authserver_url: self.authserver_url.into_static(), 189 account_did: self.account_did.into_static(), 190 scopes: self.scopes.into_static(), 191 }
+13 -9
crates/jacquard/src/client/token.rs
··· 189 pub dpop_authserver_nonce: Option<String>, 190 } 191 192 - impl From<AuthRequestData<'_>> for OAuthState { 193 - fn from(value: AuthRequestData) -> Self { 194 - OAuthState { 195 - authserver_url: value.authserver_url, 196 account_did: value.account_did.map(|s| s.to_string()), 197 scopes: value.scopes.into_iter().map(|s| s.to_string()).collect(), 198 request_uri: value.request_uri.to_string(), ··· 204 dpop_key: value.dpop_data.dpop_key, 205 dpop_authserver_nonce: value.dpop_data.dpop_authserver_nonce.map(|s| s.to_string()), 206 state: value.state.to_string(), 207 - } 208 } 209 } 210 211 impl From<OAuthState> for AuthRequestData<'_> { 212 fn from(value: OAuthState) -> Self { 213 AuthRequestData { 214 - authserver_url: value.authserver_url, 215 state: value.state.to_cowstr(), 216 account_did: value.account_did.map(|s| Did::from(s).into_static()), 217 authserver_revocation_endpoint: value ··· 317 auth_req_info: &AuthRequestData<'_>, 318 ) -> Result<(), SessionStoreError> { 319 let key = format!("authreq_{}", auth_req_info.state); 320 - self.0 321 - .set(key, StoredSession::OAuthState(auth_req_info.clone().into())) 322 - .await?; 323 Ok(()) 324 } 325
··· 189 pub dpop_authserver_nonce: Option<String>, 190 } 191 192 + impl TryFrom<AuthRequestData<'_>> for OAuthState { 193 + type Error = url::ParseError; 194 + 195 + fn try_from(value: AuthRequestData) -> Result<Self, Self::Error> { 196 + Ok(OAuthState { 197 + authserver_url: Url::parse(&value.authserver_url)?, 198 account_did: value.account_did.map(|s| s.to_string()), 199 scopes: value.scopes.into_iter().map(|s| s.to_string()).collect(), 200 request_uri: value.request_uri.to_string(), ··· 206 dpop_key: value.dpop_data.dpop_key, 207 dpop_authserver_nonce: value.dpop_data.dpop_authserver_nonce.map(|s| s.to_string()), 208 state: value.state.to_string(), 209 + }) 210 } 211 } 212 213 impl From<OAuthState> for AuthRequestData<'_> { 214 fn from(value: OAuthState) -> Self { 215 AuthRequestData { 216 + authserver_url: value.authserver_url.to_cowstr(), 217 state: value.state.to_cowstr(), 218 account_did: value.account_did.map(|s| Did::from(s).into_static()), 219 authserver_revocation_endpoint: value ··· 319 auth_req_info: &AuthRequestData<'_>, 320 ) -> Result<(), SessionStoreError> { 321 let key = format!("authreq_{}", auth_req_info.state); 322 + let state = auth_req_info 323 + .clone() 324 + .try_into() 325 + .map_err(|e: url::ParseError| SessionStoreError::Other(Box::new(e)))?; 326 + self.0.set(key, StoredSession::OAuthState(state)).await?; 327 Ok(()) 328 } 329