A library for ATProtocol identities.

refactor: refactoring location of url utils

Changed files
+134 -16
crates
+2
Cargo.lock
··· 151 "thiserror 2.0.12", 152 "tokio", 153 "tracing", 154 "zeroize", 155 ] 156 ··· 213 version = "0.11.3" 214 dependencies = [ 215 "anyhow", 216 "atproto-oauth", 217 "chrono", 218 "reqwest",
··· 151 "thiserror 2.0.12", 152 "tokio", 153 "tracing", 154 + "urlencoding", 155 "zeroize", 156 ] 157 ··· 214 version = "0.11.3" 215 dependencies = [ 216 "anyhow", 217 + "atproto-identity", 218 "atproto-oauth", 219 "chrono", 220 "reqwest",
+1 -1
crates/atproto-client/src/com_atproto_identity.rs
··· 6 use std::collections::HashMap; 7 8 use anyhow::Result; 9 use serde::{Deserialize, de::DeserializeOwned}; 10 11 use crate::{ 12 client::{Auth, get_apppassword_json, get_dpop_json, get_json}, 13 errors::SimpleError, 14 - url::URLBuilder, 15 }; 16 17 /// Response from the com.atproto.identity.resolveHandle XRPC method.
··· 6 use std::collections::HashMap; 7 8 use anyhow::Result; 9 + use atproto_identity::url::URLBuilder; 10 use serde::{Deserialize, de::DeserializeOwned}; 11 12 use crate::{ 13 client::{Auth, get_apppassword_json, get_dpop_json, get_json}, 14 errors::SimpleError, 15 }; 16 17 /// Response from the com.atproto.identity.resolveHandle XRPC method.
+1 -1
crates/atproto-client/src/com_atproto_repo.rs
··· 25 use std::collections::HashMap; 26 27 use anyhow::Result; 28 use bytes::Bytes; 29 use serde::{Deserialize, Serialize, de::DeserializeOwned}; 30 ··· 34 post_dpop_json, post_json, 35 }, 36 errors::SimpleError, 37 - url::URLBuilder, 38 }; 39 40 /// Response from getting a record from an AT Protocol repository.
··· 25 use std::collections::HashMap; 26 27 use anyhow::Result; 28 + use atproto_identity::url::URLBuilder; 29 use bytes::Bytes; 30 use serde::{Deserialize, Serialize, de::DeserializeOwned}; 31 ··· 35 post_dpop_json, post_json, 36 }, 37 errors::SimpleError, 38 }; 39 40 /// Response from getting a record from an AT Protocol repository.
+2 -1
crates/atproto-client/src/com_atproto_server.rs
··· 18 //! an access JWT token from an authenticated session. 19 20 use anyhow::Result; 21 use serde::{Deserialize, Serialize}; 22 23 - use crate::{client::post_json, url::URLBuilder}; 24 25 /// Request to create a new authentication session. 26 #[cfg_attr(debug_assertions, derive(Debug))]
··· 18 //! an access JWT token from an authenticated session. 19 20 use anyhow::Result; 21 + use atproto_identity::url::URLBuilder; 22 use serde::{Deserialize, Serialize}; 23 24 + use crate::client::post_json; 25 26 /// Request to create a new authentication session. 27 #[cfg_attr(debug_assertions, derive(Debug))]
-1
crates/atproto-client/src/lib.rs
··· 20 21 pub mod client; 22 pub mod errors; 23 - pub mod url; 24 25 mod com_atproto_identity; 26 mod com_atproto_repo;
··· 20 21 pub mod client; 22 pub mod errors; 23 24 mod com_atproto_identity; 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 45 [dependencies] 46 anyhow.workspace = true 47 ecdsa.workspace = true 48 hickory-resolver = { workspace = true, optional = true } 49 k256 = { workspace = true, features = ["jwk"] } 50 multibase.workspace = true 51 p256 = { workspace = true, features = ["jwk"] } 52 p384 = { workspace = true, features = ["jwk"] } 53 reqwest.workspace = true 54 serde_ipld_dagcbor.workspace = true 55 serde_json.workspace = true ··· 57 thiserror.workspace = true 58 tokio.workspace = true 59 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 zeroize = { workspace = true, optional = true } 66 67 [features]
··· 44 45 [dependencies] 46 anyhow.workspace = true 47 + async-trait.workspace = true 48 + clap = { workspace = true, optional = true } 49 ecdsa.workspace = true 50 + elliptic-curve.workspace = true 51 hickory-resolver = { workspace = true, optional = true } 52 k256 = { workspace = true, features = ["jwk"] } 53 + lru = { workspace = true, optional = true } 54 multibase.workspace = true 55 p256 = { workspace = true, features = ["jwk"] } 56 p384 = { workspace = true, features = ["jwk"] } 57 + rand.workspace = true 58 reqwest.workspace = true 59 serde_ipld_dagcbor.workspace = true 60 serde_json.workspace = true ··· 62 thiserror.workspace = true 63 tokio.workspace = true 64 tracing.workspace = true 65 + urlencoding.workspace = true 66 zeroize = { workspace = true, optional = true } 67 68 [features]
+1
crates/atproto-identity/src/lib.rs
··· 22 pub mod storage; 23 #[cfg(feature = "lru")] 24 pub mod storage_lru; 25 pub mod validation; 26 pub mod web;
··· 22 pub mod storage; 23 #[cfg(feature = "lru")] 24 pub mod storage_lru; 25 + pub mod url; 26 pub mod validation; 27 pub mod web;
+1
crates/atproto-oauth-aip/Cargo.toml
··· 16 17 [dependencies] 18 atproto-oauth.workspace = true 19 20 anyhow.workspace = true 21 chrono.workspace = true
··· 16 17 [dependencies] 18 atproto-oauth.workspace = true 19 + atproto-identity.workspace = true 20 21 anyhow.workspace = true 22 chrono.workspace = true
+2 -1
crates/atproto-oauth-aip/src/lib.rs
··· 80 //! let session = session_exchange( 81 //! &http_client, 82 //! &protected_resource.resource, 83 - //! &token_response.access_token 84 //! ).await?; 85 //! # Ok(()) 86 //! # }
··· 80 //! let session = session_exchange( 81 //! &http_client, 82 //! &protected_resource.resource, 83 + //! &token_response.access_token, 84 + //! &None 85 //! ).await?; 86 //! # Ok(()) 87 //! # }
+118 -6
crates/atproto-oauth-aip/src/workflow.rs
··· 99 //! let session = session_exchange( 100 //! &http_client, 101 //! &protected_resource.resource, 102 - //! &token_response.access_token 103 //! ).await?; 104 //! # Ok(()) 105 //! # } ··· 112 //! and protocol violations. 113 114 use anyhow::Result; 115 use atproto_oauth::{ 116 jwk::WrappedJsonWebKey, 117 workflow::{OAuthRequest, OAuthRequestState, ParResponse, TokenResponse}, ··· 387 /// DID, handle, and PDS endpoint. This is specific to AT Protocol's OAuth 388 /// implementation. 389 /// 390 /// # Arguments 391 /// 392 /// * `http_client` - The HTTP client to use for making requests 393 - /// * `protected_resource` - The protected resource metadata 394 /// * `access_token` - The OAuth access token to exchange 395 /// 396 /// # Returns ··· 421 protected_resource_base: &str, 422 access_token: &str, 423 ) -> Result<ATProtocolSession> { 424 let response = http_client 425 - .get(format!( 426 - "{}/api/atprotocol/session", 427 - protected_resource_base 428 - )) 429 .bearer_auth(access_token) 430 .send() 431 .await
··· 99 //! let session = session_exchange( 100 //! &http_client, 101 //! &protected_resource.resource, 102 + //! &token_response.access_token, 103 + //! &None 104 //! ).await?; 105 //! # Ok(()) 106 //! # } ··· 113 //! and protocol violations. 114 115 use anyhow::Result; 116 + use atproto_identity::url::URLBuilder; 117 use atproto_oauth::{ 118 jwk::WrappedJsonWebKey, 119 workflow::{OAuthRequest, OAuthRequestState, ParResponse, TokenResponse}, ··· 389 /// DID, handle, and PDS endpoint. This is specific to AT Protocol's OAuth 390 /// implementation. 391 /// 392 + /// This is a convenience function that calls `session_exchange_with_options` 393 + /// with no additional options. 394 + /// 395 /// # Arguments 396 /// 397 /// * `http_client` - The HTTP client to use for making requests 398 + /// * `protected_resource_base` - The base URL of the protected resource (PDS) 399 /// * `access_token` - The OAuth access token to exchange 400 /// 401 /// # Returns ··· 426 protected_resource_base: &str, 427 access_token: &str, 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 + 539 let response = http_client 540 + .get(url) 541 .bearer_auth(access_token) 542 .send() 543 .await