A library for ATProtocol identities.

refactor: refactoring location of url utils

Changed files
+134 -16
crates
+2
Cargo.lock
··· 151 151 "thiserror 2.0.12", 152 152 "tokio", 153 153 "tracing", 154 + "urlencoding", 154 155 "zeroize", 155 156 ] 156 157 ··· 213 214 version = "0.11.3" 214 215 dependencies = [ 215 216 "anyhow", 217 + "atproto-identity", 216 218 "atproto-oauth", 217 219 "chrono", 218 220 "reqwest",
+1 -1
crates/atproto-client/src/com_atproto_identity.rs
··· 6 6 use std::collections::HashMap; 7 7 8 8 use anyhow::Result; 9 + use atproto_identity::url::URLBuilder; 9 10 use serde::{Deserialize, de::DeserializeOwned}; 10 11 11 12 use crate::{ 12 13 client::{Auth, get_apppassword_json, get_dpop_json, get_json}, 13 14 errors::SimpleError, 14 - url::URLBuilder, 15 15 }; 16 16 17 17 /// Response from the com.atproto.identity.resolveHandle XRPC method.
+1 -1
crates/atproto-client/src/com_atproto_repo.rs
··· 25 25 use std::collections::HashMap; 26 26 27 27 use anyhow::Result; 28 + use atproto_identity::url::URLBuilder; 28 29 use bytes::Bytes; 29 30 use serde::{Deserialize, Serialize, de::DeserializeOwned}; 30 31 ··· 34 35 post_dpop_json, post_json, 35 36 }, 36 37 errors::SimpleError, 37 - url::URLBuilder, 38 38 }; 39 39 40 40 /// Response from getting a record from an AT Protocol repository.
+2 -1
crates/atproto-client/src/com_atproto_server.rs
··· 18 18 //! an access JWT token from an authenticated session. 19 19 20 20 use anyhow::Result; 21 + use atproto_identity::url::URLBuilder; 21 22 use serde::{Deserialize, Serialize}; 22 23 23 - use crate::{client::post_json, url::URLBuilder}; 24 + use crate::client::post_json; 24 25 25 26 /// Request to create a new authentication session. 26 27 #[cfg_attr(debug_assertions, derive(Debug))]
-1
crates/atproto-client/src/lib.rs
··· 20 20 21 21 pub mod client; 22 22 pub mod errors; 23 - pub mod url; 24 23 25 24 mod com_atproto_identity; 26 25 mod com_atproto_repo;
crates/atproto-client/src/url.rs crates/atproto-identity/src/url.rs
+6 -5
crates/atproto-identity/Cargo.toml
··· 44 44 45 45 [dependencies] 46 46 anyhow.workspace = true 47 + async-trait.workspace = true 48 + clap = { workspace = true, optional = true } 47 49 ecdsa.workspace = true 50 + elliptic-curve.workspace = true 48 51 hickory-resolver = { workspace = true, optional = true } 49 52 k256 = { workspace = true, features = ["jwk"] } 53 + lru = { workspace = true, optional = true } 50 54 multibase.workspace = true 51 55 p256 = { workspace = true, features = ["jwk"] } 52 56 p384 = { workspace = true, features = ["jwk"] } 57 + rand.workspace = true 53 58 reqwest.workspace = true 54 59 serde_ipld_dagcbor.workspace = true 55 60 serde_json.workspace = true ··· 57 62 thiserror.workspace = true 58 63 tokio.workspace = true 59 64 tracing.workspace = true 60 - elliptic-curve.workspace = true 61 - rand.workspace = true 62 - async-trait = "0.1.88" 63 - lru = { workspace = true, optional = true } 64 - clap = { workspace = true, optional = true } 65 + urlencoding.workspace = true 65 66 zeroize = { workspace = true, optional = true } 66 67 67 68 [features]
+1
crates/atproto-identity/src/lib.rs
··· 22 22 pub mod storage; 23 23 #[cfg(feature = "lru")] 24 24 pub mod storage_lru; 25 + pub mod url; 25 26 pub mod validation; 26 27 pub mod web;
+1
crates/atproto-oauth-aip/Cargo.toml
··· 16 16 17 17 [dependencies] 18 18 atproto-oauth.workspace = true 19 + atproto-identity.workspace = true 19 20 20 21 anyhow.workspace = true 21 22 chrono.workspace = true
+2 -1
crates/atproto-oauth-aip/src/lib.rs
··· 80 80 //! let session = session_exchange( 81 81 //! &http_client, 82 82 //! &protected_resource.resource, 83 - //! &token_response.access_token 83 + //! &token_response.access_token, 84 + //! &None 84 85 //! ).await?; 85 86 //! # Ok(()) 86 87 //! # }
+118 -6
crates/atproto-oauth-aip/src/workflow.rs
··· 99 99 //! let session = session_exchange( 100 100 //! &http_client, 101 101 //! &protected_resource.resource, 102 - //! &token_response.access_token 102 + //! &token_response.access_token, 103 + //! &None 103 104 //! ).await?; 104 105 //! # Ok(()) 105 106 //! # } ··· 112 113 //! and protocol violations. 113 114 114 115 use anyhow::Result; 116 + use atproto_identity::url::URLBuilder; 115 117 use atproto_oauth::{ 116 118 jwk::WrappedJsonWebKey, 117 119 workflow::{OAuthRequest, OAuthRequestState, ParResponse, TokenResponse}, ··· 387 389 /// DID, handle, and PDS endpoint. This is specific to AT Protocol's OAuth 388 390 /// implementation. 389 391 /// 392 + /// This is a convenience function that calls `session_exchange_with_options` 393 + /// with no additional options. 394 + /// 390 395 /// # Arguments 391 396 /// 392 397 /// * `http_client` - The HTTP client to use for making requests 393 - /// * `protected_resource` - The protected resource metadata 398 + /// * `protected_resource_base` - The base URL of the protected resource (PDS) 394 399 /// * `access_token` - The OAuth access token to exchange 395 400 /// 396 401 /// # Returns ··· 421 426 protected_resource_base: &str, 422 427 access_token: &str, 423 428 ) -> Result<ATProtocolSession> { 429 + session_exchange_with_options( 430 + http_client, 431 + protected_resource_base, 432 + access_token, 433 + &None, 434 + &None, 435 + ) 436 + .await 437 + } 438 + 439 + /// Exchanges an OAuth access token for an AT Protocol session with additional options. 440 + /// 441 + /// This function takes an OAuth access token and exchanges it for a full 442 + /// AT Protocol session, which includes additional information like the user's 443 + /// DID, handle, and PDS endpoint. This version allows specifying additional 444 + /// options for the session exchange. 445 + /// 446 + /// # Arguments 447 + /// 448 + /// * `http_client` - The HTTP client to use for making requests 449 + /// * `protected_resource_base` - The base URL of the protected resource (PDS) 450 + /// * `access_token` - The OAuth access token to exchange 451 + /// * `access_token_type` - Optional token type ("oauth_session", "app_password_session", or "best") 452 + /// * `subject` - Optional subject (DID) to specify which user's session to retrieve 453 + /// 454 + /// # Returns 455 + /// 456 + /// Returns an `ATProtocolSession` with full session information, 457 + /// or an error if the session exchange fails. 458 + /// 459 + /// # Example 460 + /// 461 + /// ```no_run 462 + /// # async fn example() -> Result<(), Box<dyn std::error::Error>> { 463 + /// use atproto_oauth_aip::workflow::session_exchange_with_options; 464 + /// # let http_client = reqwest::Client::new(); 465 + /// # let protected_resource = "https://pds.example.com"; 466 + /// # let access_token = "example_token"; 467 + /// // Basic usage without options 468 + /// let session = session_exchange_with_options( 469 + /// &http_client, 470 + /// protected_resource, 471 + /// access_token, 472 + /// &None, 473 + /// &None, 474 + /// ).await?; 475 + /// # Ok(()) 476 + /// # } 477 + /// ``` 478 + /// 479 + /// # Example with access_token_type 480 + /// 481 + /// ```no_run 482 + /// # async fn example() -> Result<(), Box<dyn std::error::Error>> { 483 + /// use atproto_oauth_aip::workflow::session_exchange_with_options; 484 + /// # let http_client = reqwest::Client::new(); 485 + /// # let protected_resource = "https://pds.example.com"; 486 + /// # let access_token = "example_token"; 487 + /// // Specify the token type 488 + /// let session = session_exchange_with_options( 489 + /// &http_client, 490 + /// protected_resource, 491 + /// access_token, 492 + /// &Some("oauth_session"), 493 + /// &None, 494 + /// ).await?; 495 + /// # Ok(()) 496 + /// # } 497 + /// ``` 498 + /// 499 + /// # Example with subject 500 + /// 501 + /// ```no_run 502 + /// # async fn example() -> Result<(), Box<dyn std::error::Error>> { 503 + /// use atproto_oauth_aip::workflow::session_exchange_with_options; 504 + /// # let http_client = reqwest::Client::new(); 505 + /// # let protected_resource = "https://pds.example.com"; 506 + /// # let access_token = "example_token"; 507 + /// # let user_did = "did:plc:example123"; 508 + /// // Specify both token type and subject 509 + /// let session = session_exchange_with_options( 510 + /// &http_client, 511 + /// protected_resource, 512 + /// access_token, 513 + /// &Some("app_password_session"), 514 + /// &Some(user_did), 515 + /// ).await?; 516 + /// # Ok(()) 517 + /// # } 518 + /// ``` 519 + pub async fn session_exchange_with_options( 520 + http_client: &reqwest::Client, 521 + protected_resource_base: &str, 522 + access_token: &str, 523 + access_token_type: &Option<&str>, 524 + subject: &Option<&str>, 525 + ) -> Result<ATProtocolSession> { 526 + let mut url_builder = URLBuilder::new(protected_resource_base); 527 + url_builder.path("/api/atprotocol/session"); 528 + 529 + if let Some(value) = access_token_type { 530 + url_builder.param("access_token_type", value); 531 + } 532 + 533 + if let Some(value) = subject { 534 + url_builder.param("sub", value); 535 + } 536 + 537 + let url = url_builder.build(); 538 + 424 539 let response = http_client 425 - .get(format!( 426 - "{}/api/atprotocol/session", 427 - protected_resource_base 428 - )) 540 + .get(url) 429 541 .bearer_auth(access_token) 430 542 .send() 431 543 .await