A better Rust ATProto crate
0
fork

Configure Feed

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

import cleanup and fixing some client metadata failures due to adjusting behaviour earlier

Orual f934cf26 40c7d5ab

+30 -39
+1 -1
crates/jacquard-common/src/error.rs
··· 149 149 RefreshFailed, 150 150 151 151 /// Request requires authentication but none was provided 152 - #[error("No authentication provided")] 152 + #[error("No authentication provided, but endpoint requires auth")] 153 153 NotAuthenticated, 154 154 155 155 /// Other authentication error
+18 -17
crates/jacquard-oauth/src/atproto.rs
··· 232 232 Url::from_str("http://127.0.0.1/").unwrap(), 233 233 Url::from_str("http://[::1]/").unwrap(), 234 234 ], 235 - scope: None, 235 + scope: Some(CowStr::new_static("atproto")), 236 236 grant_types: None, 237 237 token_endpoint_auth_method: Some(AuthMethod::None.into()), 238 238 dpop_bound_access_tokens: None, ··· 262 262 .expect("failed to convert metadata"), 263 263 OAuthClientMetadata { 264 264 client_id: Url::from_str( 265 - "http://localhost?redirect_uri=http%3A%2F%2Flocalhost%2Fcallback&redirect_uri=http%3A%2F%2Flocalhost%2Fcallback&scope=account%3Aemail+atproto+transition%3Ageneric" 265 + "http://localhost?redirect_uri=http%3A%2F%2F127.0.0.1%2Fcallback&redirect_uri=http%3A%2F%2F127.0.0.1%2Fcallback&scope=account%3Aemail+atproto+transition%3Ageneric" 266 266 ).unwrap(), 267 267 client_uri: None, 268 268 redirect_uris: vec![ 269 - Url::from_str("http://localhost/callback").unwrap(), 270 - Url::from_str("http://localhost/callback").unwrap(), 269 + Url::from_str("http://127.0.0.1/callback").unwrap(), 270 + // TODO: fix this so that it respects IPv6 271 + Url::from_str("http://127.0.0.1/callback").unwrap(), 271 272 ], 272 - scope: None, 273 + scope: Some(CowStr::new_static("account:email atproto transition:generic")), 273 274 grant_types: None, 274 275 token_endpoint_auth_method: Some(AuthMethod::None.into()), 275 276 dpop_bound_access_tokens: None, ··· 291 292 ), 292 293 &None, 293 294 ) 294 - .expect("should coerce to localhost"); 295 + .expect("should coerce to 127.0.0.1"); 295 296 assert_eq!( 296 297 out, 297 298 OAuthClientMetadata { 298 299 client_id: Url::from_str( 299 - "http://localhost?redirect_uri=http%3A%2F%2Flocalhost%2F" 300 + "http://localhost?redirect_uri=http%3A%2F%2F127.0.0.1%2F" 300 301 ) 301 302 .unwrap(), 302 303 client_uri: None, 303 - redirect_uris: vec![Url::from_str("http://localhost/").unwrap()], 304 - scope: None, 304 + redirect_uris: vec![Url::from_str("http://127.0.0.1/").unwrap()], 305 + scope: Some(CowStr::new_static("atproto")), 305 306 grant_types: None, 306 307 token_endpoint_auth_method: Some(AuthMethod::None.into()), 307 308 dpop_bound_access_tokens: None, ··· 319 320 ), 320 321 &None, 321 322 ) 322 - .expect("should coerce to localhost"); 323 + .expect("should coerce to 127.0.0.1"); 323 324 assert_eq!( 324 325 out, 325 326 OAuthClientMetadata { 326 327 client_id: Url::from_str( 327 - "http://localhost?redirect_uri=http%3A%2F%2Flocalhost%2F" 328 + "http://localhost?redirect_uri=http%3A%2F%2F127.0.0.1%3A8000%2F" 328 329 ) 329 330 .unwrap(), 330 331 client_uri: None, 331 - redirect_uris: vec![Url::from_str("http://localhost/").unwrap()], 332 - scope: None, 332 + redirect_uris: vec![Url::from_str("http://127.0.0.1:8000/").unwrap()], 333 + scope: Some(CowStr::new_static("atproto")), 333 334 grant_types: None, 334 335 token_endpoint_auth_method: Some(AuthMethod::None.into()), 335 336 dpop_bound_access_tokens: None, ··· 347 348 ), 348 349 &None, 349 350 ) 350 - .expect("should coerce to localhost"); 351 + .expect("should coerce to 127.0.0.1"); 351 352 assert_eq!( 352 353 out, 353 354 OAuthClientMetadata { 354 355 client_id: Url::from_str( 355 - "http://localhost?redirect_uri=http%3A%2F%2Flocalhost%2F" 356 + "http://localhost?redirect_uri=http%3A%2F%2F127.0.0.1%2F" 356 357 ) 357 358 .unwrap(), 358 359 client_uri: None, 359 - redirect_uris: vec![Url::from_str("http://localhost/").unwrap()], 360 - scope: None, 360 + redirect_uris: vec![Url::from_str("http://127.0.0.1/").unwrap()], 361 + scope: Some(CowStr::new_static("atproto")), 361 362 grant_types: None, 362 363 token_endpoint_auth_method: Some(AuthMethod::None.into()), 363 364 dpop_bound_access_tokens: None,
+11 -21
crates/jacquard-oauth/src/loopback.rs
··· 10 10 scopes::Scope, 11 11 types::{AuthorizeOptions, CallbackParams}, 12 12 }; 13 - use jacquard_common::{CowStr, IntoStatic, cowstr::ToCowStr}; 13 + use jacquard_common::{IntoStatic, cowstr::ToCowStr}; 14 14 use rouille::Server; 15 - use std::{net::SocketAddr, sync::Arc}; 16 - use tokio::{ 17 - net::TcpListener, 18 - sync::{Mutex, mpsc, oneshot}, 19 - }; 15 + use std::net::SocketAddr; 16 + use tokio::sync::mpsc; 20 17 use url::Url; 21 18 22 19 #[derive(Clone, Debug)] ··· 108 105 opts: AuthorizeOptions<'_>, 109 106 cfg: LoopbackConfig, 110 107 ) -> crate::error::Result<super::client::OAuthSession<T, S>> { 111 - // 1) Bind server first to learn effective port 112 108 let port = match cfg.port { 113 109 LoopbackPort::Fixed(p) => p, 114 110 LoopbackPort::Ephemeral => 0, 115 111 }; 116 - // TODO: fix this to it also accepts ipv6 112 + // TODO: fix this to it also accepts ipv6 and properly finds a free port 117 113 let bind_addr: SocketAddr = format!("0.0.0.0:{}", port) 118 114 .parse() 119 115 .expect("invalid loopback host/port"); 120 116 let (local_addr, handle) = one_shot_server(bind_addr); 121 117 println!("Listening on {}", local_addr); 122 - 123 - // 2) Build per-flow metadata with the actual redirect URI 118 + // build redirect uri 124 119 let redirect = Url::parse(&format!( 125 120 "http://{}:{}/oauth/callback", 126 121 cfg.host, ··· 138 133 ), 139 134 }; 140 135 141 - // Build a per-flow client using shared store and resolver 136 + // Build client using store and resolver 142 137 let flow_client = OAuthClient::new_with_shared( 143 138 self.registry.store.clone(), 144 139 self.client.clone(), 145 140 client_data.clone(), 146 141 ); 147 142 148 - // 3) Start auth (persists state) and get authorization URL 143 + // Start auth and get authorization URL 149 144 let auth_url = flow_client.start_auth(input.as_ref(), opts).await?; 150 145 // Print URL for copy/paste 151 - println!("Open this URL to authorize:\n{}\n", auth_url); 146 + println!("To authenticate with your PDS, visit:\n{}\n", auth_url); 152 147 // Optionally open browser 153 148 if cfg.open_browser { 154 149 let _ = try_open_in_browser(&auth_url); 155 150 } 156 151 157 - // 4) Await callback or timeout 152 + // Await callback or timeout 158 153 let mut callback_rx = handle.callback_rx; 159 154 let cb = tokio::time::timeout( 160 155 std::time::Duration::from_millis(cfg.timeout_ms), ··· 163 158 .await; 164 159 // trigger shutdown 165 160 let _ = handle.server_stop.send(()); 166 - if let Err(_) = cb { 167 - return Err(OAuthError::Callback(CallbackError::Timeout)); 168 - } 169 - 170 161 if let Ok(Some(cb)) = cb { 171 - // 5) Continue with callback flow 172 - let session = flow_client.callback(cb).await?; 173 - Ok(session) 162 + // Handle callback and create a session 163 + Ok(flow_client.callback(cb).await?) 174 164 } else { 175 165 Err(OAuthError::Callback(CallbackError::Timeout)) 176 166 }