A better Rust ATProto crate

moved identity resolution stuff into its own crate

Orual 102d6666 36ef115b

Changed files
+134 -81
crates
+38 -3
Cargo.lock
··· 1539 "bon", 1540 "bytes", 1541 "clap", 1542 - "hickory-resolver", 1543 "http", 1544 "jacquard-api", 1545 "jacquard-common", 1546 "jacquard-derive", 1547 "jacquard-oauth", 1548 "jose-jwk", 1549 "miette", ··· 1588 "cid", 1589 "ed25519-dalek", 1590 "enum_dispatch", 1591 - "hickory-resolver", 1592 "http", 1593 "ipld-core", 1594 "k256", ··· 1610 "smol_str", 1611 "thiserror 2.0.17", 1612 "tokio", 1613 "url", 1614 ] 1615 ··· 1631 ] 1632 1633 [[package]] 1634 name = "jacquard-lexicon" 1635 version = "0.2.0" 1636 dependencies = [ ··· 1657 dependencies = [ 1658 "async-trait", 1659 "base64 0.22.1", 1660 - "bon", 1661 "chrono", 1662 "dashmap", 1663 "elliptic-curve", 1664 "http", 1665 "jacquard-common", 1666 "jose-jwa", 1667 "jose-jwk", 1668 "miette", ··· 1678 "smol_str", 1679 "thiserror 2.0.17", 1680 "tokio", 1681 "url", 1682 "uuid", 1683 ] ··· 3344 checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 3345 dependencies = [ 3346 "once_cell", 3347 ] 3348 3349 [[package]]
··· 1539 "bon", 1540 "bytes", 1541 "clap", 1542 "http", 1543 "jacquard-api", 1544 "jacquard-common", 1545 "jacquard-derive", 1546 + "jacquard-identity", 1547 "jacquard-oauth", 1548 "jose-jwk", 1549 "miette", ··· 1588 "cid", 1589 "ed25519-dalek", 1590 "enum_dispatch", 1591 "http", 1592 "ipld-core", 1593 "k256", ··· 1609 "smol_str", 1610 "thiserror 2.0.17", 1611 "tokio", 1612 + "trait-variant", 1613 "url", 1614 ] 1615 ··· 1631 ] 1632 1633 [[package]] 1634 + name = "jacquard-identity" 1635 + version = "0.2.0" 1636 + dependencies = [ 1637 + "async-trait", 1638 + "bon", 1639 + "bytes", 1640 + "hickory-resolver", 1641 + "http", 1642 + "jacquard-api", 1643 + "jacquard-common", 1644 + "miette", 1645 + "percent-encoding", 1646 + "reqwest", 1647 + "serde", 1648 + "serde_html_form", 1649 + "serde_json", 1650 + "thiserror 2.0.17", 1651 + "tokio", 1652 + "url", 1653 + "urlencoding", 1654 + ] 1655 + 1656 + [[package]] 1657 name = "jacquard-lexicon" 1658 version = "0.2.0" 1659 dependencies = [ ··· 1680 dependencies = [ 1681 "async-trait", 1682 "base64 0.22.1", 1683 "chrono", 1684 "dashmap", 1685 "elliptic-curve", 1686 "http", 1687 "jacquard-common", 1688 + "jacquard-identity", 1689 "jose-jwa", 1690 "jose-jwk", 1691 "miette", ··· 1701 "smol_str", 1702 "thiserror 2.0.17", 1703 "tokio", 1704 + "trait-variant", 1705 "url", 1706 "uuid", 1707 ] ··· 3368 checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 3369 dependencies = [ 3370 "once_cell", 3371 + ] 3372 + 3373 + [[package]] 3374 + name = "trait-variant" 3375 + version = "0.1.2" 3376 + source = "registry+https://github.com/rust-lang/crates.io-index" 3377 + checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" 3378 + dependencies = [ 3379 + "proc-macro2", 3380 + "quote", 3381 + "syn 2.0.106", 3382 ] 3383 3384 [[package]]
+6
Cargo.toml
··· 35 miette = "7.6" 36 thiserror = "2.0" 37 38 # Data types 39 bytes = "1.10" 40 smol_str = { version = "0.3", features = ["serde"] }
··· 35 miette = "7.6" 36 thiserror = "2.0" 37 38 + # trait stuff 39 + trait-variant = "0.1.2" 40 + 41 + 42 + bon = "3.8.0" 43 + 44 # Data types 45 bytes = "1.10" 46 smol_str = { version = "0.3", features = ["serde"] }
+1 -2
crates/jacquard-common/Cargo.toml
··· 39 async-trait = "0.1" 40 tokio = { version = "1", features = ["sync"] } 41 reqwest = { workspace = true, optional = true, features = ["charset", "http2", "json", "system-proxy", "gzip", "rustls-tls"] } 42 - hickory-resolver = { version = "0.24", default-features = false, features = ["system-config", "tokio-runtime"], optional = true } 43 serde_ipld_dagcbor.workspace = true 44 45 [features] 46 default = [] 47 - dns = ["dep:hickory-resolver"] 48 crypto = [] 49 crypto-ed25519 = ["crypto", "dep:ed25519-dalek"] 50 crypto-k256 = ["crypto", "dep:k256"]
··· 39 async-trait = "0.1" 40 tokio = { version = "1", features = ["sync"] } 41 reqwest = { workspace = true, optional = true, features = ["charset", "http2", "json", "system-proxy", "gzip", "rustls-tls"] } 42 serde_ipld_dagcbor.workspace = true 43 + trait-variant.workspace = true 44 45 [features] 46 default = [] 47 crypto = [] 48 crypto-ed25519 = ["crypto", "dep:ed25519-dalek"] 49 crypto-k256 = ["crypto", "dep:k256"]
+2 -2
crates/jacquard-common/src/cowstr.rs
··· 1 - use serde::{Deserialize, Serialize, de::DeserializeOwned}; 2 - use smol_str::{SmolStr, ToSmolStr}; 3 use std::{ 4 borrow::Cow, 5 fmt,
··· 1 + use serde::{Deserialize, Serialize}; 2 + use smol_str::SmolStr; 3 use std::{ 4 borrow::Cow, 5 fmt,
+12 -12
crates/jacquard-common/src/ident_resolver.rs crates/jacquard-identity/src/resolver.rs
··· 12 use std::collections::BTreeMap; 13 use std::str::FromStr; 14 15 - use crate::error::TransportError; 16 - use crate::types::did_doc::Service; 17 - use crate::types::ident::AtIdentifier; 18 - use crate::types::string::AtprotoStr; 19 - use crate::types::uri::Uri; 20 - use crate::types::value::Data; 21 - use crate::{CowStr, IntoStatic}; 22 use bon::Builder; 23 use bytes::Bytes; 24 use http::StatusCode; 25 use miette::Diagnostic; 26 use thiserror::Error; 27 use url::Url; 28 29 - use crate::types::did_doc::DidDocument; 30 - use crate::types::string::{Did, Handle}; 31 - use crate::types::value::AtDataError; 32 /// Errors that can occur during identity resolution. 33 /// 34 /// Note: when validating a fetched DID document against a requested DID, a ··· 114 /// mismatch). Use `into_owned()` to parse into an owned document. 115 #[derive(Clone)] 116 pub struct DidDocResponse { 117 pub buffer: Bytes, 118 pub status: StatusCode, 119 /// Optional DID we intended to resolve; used for validation helpers 120 pub requested: Option<Did<'static>>, ··· 205 #[serde(borrow)] 206 pub handle: Handle<'a>, 207 #[serde(borrow)] 208 - pub pds: crate::CowStr<'a>, 209 #[serde(borrow, rename = "signingKey", alias = "signing_key")] 210 - pub signing_key: crate::CowStr<'a>, 211 } 212 213 /// Handle → DID fallback step.
··· 12 use std::collections::BTreeMap; 13 use std::str::FromStr; 14 15 use bon::Builder; 16 use bytes::Bytes; 17 use http::StatusCode; 18 + use jacquard_common::error::TransportError; 19 + use jacquard_common::types::did::Did; 20 + use jacquard_common::types::did_doc::{DidDocument, Service}; 21 + use jacquard_common::types::ident::AtIdentifier; 22 + use jacquard_common::types::string::{AtprotoStr, Handle}; 23 + use jacquard_common::types::uri::Uri; 24 + use jacquard_common::types::value::{AtDataError, Data}; 25 + use jacquard_common::{CowStr, IntoStatic}; 26 use miette::Diagnostic; 27 use thiserror::Error; 28 use url::Url; 29 30 /// Errors that can occur during identity resolution. 31 /// 32 /// Note: when validating a fetched DID document against a requested DID, a ··· 112 /// mismatch). Use `into_owned()` to parse into an owned document. 113 #[derive(Clone)] 114 pub struct DidDocResponse { 115 + #[allow(missing_docs)] 116 pub buffer: Bytes, 117 + #[allow(missing_docs)] 118 pub status: StatusCode, 119 /// Optional DID we intended to resolve; used for validation helpers 120 pub requested: Option<Did<'static>>, ··· 205 #[serde(borrow)] 206 pub handle: Handle<'a>, 207 #[serde(borrow)] 208 + pub pds: CowStr<'a>, 209 #[serde(borrow, rename = "signingKey", alias = "signing_key")] 210 + pub signing_key: CowStr<'a>, 211 } 212 213 /// Handle → DID fallback step.
-1
crates/jacquard-common/src/lib.rs
··· 16 pub mod error; 17 /// HTTP client abstraction used by jacquard crates. 18 pub mod http_client; 19 - pub mod ident_resolver; 20 pub mod macros; 21 /// Generic session storage traits and utilities. 22 pub mod session;
··· 16 pub mod error; 17 /// HTTP client abstraction used by jacquard crates. 18 pub mod http_client; 19 pub mod macros; 20 /// Generic session storage traits and utilities. 21 pub mod session;
+35
crates/jacquard-identity/Cargo.toml
···
··· 1 + [package] 2 + name = "jacquard-identity" 3 + edition.workspace = true 4 + version.workspace = true 5 + authors.workspace = true 6 + repository.workspace = true 7 + keywords.workspace = true 8 + categories.workspace = true 9 + readme.workspace = true 10 + exclude.workspace = true 11 + homepage.workspace = true 12 + license.workspace = true 13 + description.workspace = true 14 + 15 + [features] 16 + dns = ["dep:hickory-resolver"] 17 + 18 + [dependencies] 19 + async-trait = "0.1.89" 20 + bon.workspace = true 21 + bytes.workspace = true 22 + jacquard-common = { version = "0.2", path = "../jacquard-common" } 23 + percent-encoding = "2.3.2" 24 + reqwest.workspace = true 25 + url.workspace = true 26 + tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs"] } 27 + hickory-resolver = { optional = true, version = "0.24", default-features = false, features = ["system-config", "tokio-runtime"]} 28 + serde.workspace = true 29 + serde_json.workspace = true 30 + thiserror.workspace = true 31 + miette.workspace = true 32 + http.workspace = true 33 + jacquard-api = { version = "0.2.0", path = "../jacquard-api" } 34 + serde_html_form.workspace = true 35 + urlencoding = "2.1.3"
+3 -1
crates/jacquard-oauth/Cargo.toml
··· 29 async-trait = "0.1.89" 30 dashmap = "6.1.0" 31 tokio = { version = "1.47.1", features = ["sync"] } 32 - bon = "3.8.0" 33 reqwest.workspace = true
··· 29 async-trait = "0.1.89" 30 dashmap = "6.1.0" 31 tokio = { version = "1.47.1", features = ["sync"] } 32 + 33 reqwest.workspace = true 34 + trait-variant.workspace = true 35 + jacquard-identity = { version = "0.2.0", path = "../jacquard-identity" }
+1 -1
crates/jacquard-oauth/src/atproto.rs
··· 5 use jacquard_common::CowStr; 6 use serde::{Deserialize, Serialize}; 7 use thiserror::Error; 8 - use url::{Host, Url}; 9 10 #[derive(Error, Debug)] 11 pub enum Error {
··· 5 use jacquard_common::CowStr; 6 use serde::{Deserialize, Serialize}; 7 use thiserror::Error; 8 + use url::Url; 9 10 #[derive(Error, Debug)] 11 pub enum Error {
-13
crates/jacquard-oauth/src/dpop.rs
··· 63 { 64 DpopCall::client(self, data_source) 65 } 66 - 67 - async fn wrap_with_dpop<'r, D>( 68 - &'r self, 69 - is_to_auth_server: bool, 70 - data_source: &'r mut D, 71 - request: Request<Vec<u8>>, 72 - ) -> Result<Response<Vec<u8>>> 73 - where 74 - Self: Sized, 75 - D: DpopDataSource, 76 - { 77 - wrap_request_with_dpop(self, data_source, is_to_auth_server, request).await 78 - } 79 } 80 81 pub struct DpopCall<'r, C: HttpClient, D: DpopDataSource> {
··· 63 { 64 DpopCall::client(self, data_source) 65 } 66 } 67 68 pub struct DpopCall<'r, C: HttpClient, D: DpopDataSource> {
+13 -17
crates/jacquard-oauth/src/request.rs
··· 1 - use chrono::{DateTime, FixedOffset, TimeDelta, Utc}; 2 use http::{Method, Request, StatusCode}; 3 use jacquard_common::{ 4 CowStr, IntoStatic, 5 cowstr::ToCowStr, 6 http_client::HttpClient, 7 - ident_resolver::{IdentityError, IdentityResolver}, 8 session::SessionStoreError, 9 types::{ 10 did::Did, 11 string::{AtStrError, Datetime}, 12 }, 13 }; 14 - use jose_jwk::Key; 15 - use serde::{Serialize, de::DeserializeOwned}; 16 use serde_json::Value; 17 use smol_str::ToSmolStr; 18 - use std::sync::Arc; 19 use thiserror::Error; 20 - use url::Url; 21 22 use crate::{ 23 FALLBACK_ALG, 24 - atproto::{AtprotoClientMetadata, atproto_client_metadata}, 25 - dpop::{DpopClient, DpopExt}, 26 jose::jwt::{RegisteredClaims, RegisteredClaimsAud}, 27 keyset::Keyset, 28 resolver::OAuthResolver, ··· 424 } 425 } 426 427 fn endpoint_for_req<'a, 'r>( 428 server_metadata: &'r OAuthAuthorizationServerMetadata<'a>, 429 request: &'r OAuthRequest, ··· 438 } 439 } 440 441 - fn build_oauth_req_body<'a, S>( 442 - client_assertions: ClientAssertions<'a>, 443 - parameters: S, 444 - ) -> Result<String> 445 where 446 S: Serialize, 447 { ··· 454 } 455 456 #[derive(Debug, Clone, Default)] 457 - pub struct ClientAssertions<'a> { 458 client_id: CowStr<'a>, 459 assertion_type: Option<CowStr<'a>>, // either none or `CLIENT_ASSERTION_TYPE_JWT_BEARER` 460 assertion: Option<CowStr<'a>>, 461 } 462 463 - impl<'s> ClientAssertions<'s> { 464 pub fn new_id(client_id: CowStr<'s>) -> Self { 465 Self { 466 client_id, ··· 474 keyset: Option<&Keyset>, 475 server_metadata: &OAuthAuthorizationServerMetadata<'a>, 476 client_metadata: &OAuthClientMetadata<'a>, 477 - ) -> Result<ClientAssertions<'a>> { 478 let method_supported = server_metadata 479 .token_endpoint_auth_methods_supported 480 .as_ref(); ··· 494 .unwrap_or(vec![FALLBACK_ALG.into()]); 495 algs.sort_by(compare_algos); 496 let iat = Utc::now().timestamp(); 497 - return Ok(ClientAssertions { 498 client_id: client_id.clone(), 499 assertion_type: Some(CowStr::new_static(CLIENT_ASSERTION_TYPE_JWT_BEARER)), 500 assertion: Some( ··· 526 .as_ref() 527 .is_some_and(|v| v.contains(&CowStr::new_static("none"))) => 528 { 529 - return Ok(ClientAssertions::new_id(client_id)); 530 } 531 _ => {} 532 }
··· 1 + use chrono::{TimeDelta, Utc}; 2 use http::{Method, Request, StatusCode}; 3 use jacquard_common::{ 4 CowStr, IntoStatic, 5 cowstr::ToCowStr, 6 http_client::HttpClient, 7 session::SessionStoreError, 8 types::{ 9 did::Did, 10 string::{AtStrError, Datetime}, 11 }, 12 }; 13 + use jacquard_identity::resolver::IdentityError; 14 + use serde::Serialize; 15 use serde_json::Value; 16 use smol_str::ToSmolStr; 17 use thiserror::Error; 18 19 use crate::{ 20 FALLBACK_ALG, 21 + atproto::atproto_client_metadata, 22 + dpop::DpopExt, 23 jose::jwt::{RegisteredClaims, RegisteredClaimsAud}, 24 keyset::Keyset, 25 resolver::OAuthResolver, ··· 421 } 422 } 423 424 + #[inline] 425 fn endpoint_for_req<'a, 'r>( 426 server_metadata: &'r OAuthAuthorizationServerMetadata<'a>, 427 request: &'r OAuthRequest, ··· 436 } 437 } 438 439 + #[inline] 440 + fn build_oauth_req_body<'a, S>(client_assertions: ClientAuth<'a>, parameters: S) -> Result<String> 441 where 442 S: Serialize, 443 { ··· 450 } 451 452 #[derive(Debug, Clone, Default)] 453 + pub struct ClientAuth<'a> { 454 client_id: CowStr<'a>, 455 assertion_type: Option<CowStr<'a>>, // either none or `CLIENT_ASSERTION_TYPE_JWT_BEARER` 456 assertion: Option<CowStr<'a>>, 457 } 458 459 + impl<'s> ClientAuth<'s> { 460 pub fn new_id(client_id: CowStr<'s>) -> Self { 461 Self { 462 client_id, ··· 470 keyset: Option<&Keyset>, 471 server_metadata: &OAuthAuthorizationServerMetadata<'a>, 472 client_metadata: &OAuthClientMetadata<'a>, 473 + ) -> Result<ClientAuth<'a>> { 474 let method_supported = server_metadata 475 .token_endpoint_auth_methods_supported 476 .as_ref(); ··· 490 .unwrap_or(vec![FALLBACK_ALG.into()]); 491 algs.sort_by(compare_algos); 492 let iat = Utc::now().timestamp(); 493 + return Ok(ClientAuth { 494 client_id: client_id.clone(), 495 assertion_type: Some(CowStr::new_static(CLIENT_ASSERTION_TYPE_JWT_BEARER)), 496 assertion: Some( ··· 522 .as_ref() 523 .is_some_and(|v| v.contains(&CowStr::new_static("none"))) => 524 { 525 + return Ok(ClientAuth::new_id(client_id)); 526 } 527 _ => {} 528 }
+1 -5
crates/jacquard-oauth/src/resolver.rs
··· 1 use crate::types::{OAuthAuthorizationServerMetadata, OAuthProtectedResourceMetadata}; 2 use http::{Request, StatusCode}; 3 use jacquard_common::IntoStatic; 4 - use jacquard_common::ident_resolver::{IdentityError, IdentityResolver}; 5 use jacquard_common::types::did_doc::DidDocument; 6 use jacquard_common::types::ident::AtIdentifier; 7 use jacquard_common::{http_client::HttpClient, types::did::Did}; 8 - use sha2::digest::const_oid::Arc; 9 use url::Url; 10 11 #[derive(thiserror::Error, Debug, miette::Diagnostic)] ··· 160 Ok(as_metadata) 161 } 162 } 163 - 164 - #[async_trait::async_trait] 165 - impl<T: OAuthResolver + Sync + Send> OAuthResolver for std::sync::Arc<T> {} 166 167 pub async fn resolve_authorization_server<T: HttpClient + ?Sized>( 168 client: &T,
··· 1 use crate::types::{OAuthAuthorizationServerMetadata, OAuthProtectedResourceMetadata}; 2 use http::{Request, StatusCode}; 3 use jacquard_common::IntoStatic; 4 use jacquard_common::types::did_doc::DidDocument; 5 use jacquard_common::types::ident::AtIdentifier; 6 use jacquard_common::{http_client::HttpClient, types::did::Did}; 7 + use jacquard_identity::resolver::{IdentityError, IdentityResolver}; 8 use url::Url; 9 10 #[derive(thiserror::Error, Debug, miette::Diagnostic)] ··· 159 Ok(as_metadata) 160 } 161 } 162 163 pub async fn resolve_authorization_server<T: HttpClient + ?Sized>( 164 client: &T,
+2 -1
crates/jacquard-oauth/src/session.rs
··· 308 return Ok(session); 309 } 310 } 311 - let metadata = OAuthMetadata::new(&self.client, &self.client_data, &session).await?; 312 session = refresh(self.client.as_ref(), session, &metadata).await?; 313 self.store.upsert_session(session.clone()).await?; 314
··· 308 return Ok(session); 309 } 310 } 311 + let metadata = 312 + OAuthMetadata::new(self.client.as_ref(), &self.client_data, &session).await?; 313 session = refresh(self.client.as_ref(), session, &metadata).await?; 314 self.store.upsert_session(session.clone()).await?; 315
+1 -2
crates/jacquard-oauth/src/utils.rs
··· 1 use base64::Engine; 2 use base64::engine::general_purpose::URL_SAFE_NO_PAD; 3 use elliptic_curve::SecretKey; 4 - use jacquard_common::{CowStr, IntoStatic, cowstr::ToCowStr}; 5 use jose_jwk::{Key, crypto}; 6 use rand::{CryptoRng, RngCore, rngs::ThreadRng}; 7 use sha2::{Digest, Sha256}; 8 - use smol_str::ToSmolStr; 9 use std::cmp::Ordering; 10 11 use crate::{FALLBACK_ALG, types::OAuthAuthorizationServerMetadata};
··· 1 use base64::Engine; 2 use base64::engine::general_purpose::URL_SAFE_NO_PAD; 3 use elliptic_curve::SecretKey; 4 + use jacquard_common::CowStr; 5 use jose_jwk::{Key, crypto}; 6 use rand::{CryptoRng, RngCore, rngs::ThreadRng}; 7 use sha2::{Digest, Sha256}; 8 use std::cmp::Ordering; 9 10 use crate::{FALLBACK_ALG, types::OAuthAuthorizationServerMetadata};
+2 -2
crates/jacquard/Cargo.toml
··· 16 derive = ["dep:jacquard-derive"] 17 api = ["jacquard-api/com_atproto"] 18 api_all = ["api", "jacquard-api/app_bsky", "jacquard-api/chat_bsky", "jacquard-api/tools_ozone"] 19 - dns = ["dep:hickory-resolver", "jacquard-common/dns"] 20 fancy = ["miette/fancy"] 21 loopback = ["dep:rouille"] 22 ··· 47 serde_json.workspace = true 48 thiserror.workspace = true 49 tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs"] } 50 - hickory-resolver = { version = "0.24", default-features = false, features = ["system-config", "tokio-runtime"], optional = true } 51 url.workspace = true 52 smol_str.workspace = true 53 percent-encoding = "2" ··· 56 p256 = { version = "0.13", features = ["ecdsa"] } 57 rand_core = "0.6" 58 rouille = { version = "3.6.2", optional = true }
··· 16 derive = ["dep:jacquard-derive"] 17 api = ["jacquard-api/com_atproto"] 18 api_all = ["api", "jacquard-api/app_bsky", "jacquard-api/chat_bsky", "jacquard-api/tools_ozone"] 19 + dns = ["jacquard-identity/dns"] 20 fancy = ["miette/fancy"] 21 loopback = ["dep:rouille"] 22 ··· 47 serde_json.workspace = true 48 thiserror.workspace = true 49 tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs"] } 50 url.workspace = true 51 smol_str.workspace = true 52 percent-encoding = "2" ··· 55 p256 = { version = "0.13", features = ["ecdsa"] } 56 rand_core = "0.6" 57 rouille = { version = "3.6.2", optional = true } 58 + jacquard-identity = { version = "0.2.0", path = "../jacquard-identity" }
+3 -5
crates/jacquard/src/client.rs
··· 21 pub use token::FileTokenStore; 22 use url::Url; 23 24 - use p256::SecretKey; 25 - 26 // Note: Stateless and stateful XRPC clients are implemented in xrpc_call.rs and at_client.rs 27 28 pub(crate) const NSID_REFRESH_SESSION: &str = "com.atproto.server.refreshSession"; ··· 133 #[derive(Debug, Clone)] 134 pub enum AuthSession { 135 AppPassword(AtpSession), 136 - OAuth(jacquard_oauth::session::OauthSession<'static>), 137 } 138 139 impl AuthSession { ··· 187 } 188 } 189 190 - impl From<jacquard_oauth::session::OauthSession<'static>> for AuthSession { 191 - fn from(session: jacquard_oauth::session::OauthSession<'static>) -> Self { 192 AuthSession::OAuth(session) 193 } 194 }
··· 21 pub use token::FileTokenStore; 22 use url::Url; 23 24 // Note: Stateless and stateful XRPC clients are implemented in xrpc_call.rs and at_client.rs 25 26 pub(crate) const NSID_REFRESH_SESSION: &str = "com.atproto.server.refreshSession"; ··· 131 #[derive(Debug, Clone)] 132 pub enum AuthSession { 133 AppPassword(AtpSession), 134 + OAuth(jacquard_oauth::session::ClientSessionData<'static>), 135 } 136 137 impl AuthSession { ··· 185 } 186 } 187 188 + impl From<jacquard_oauth::session::ClientSessionData<'static>> for AuthSession { 189 + fn from(session: jacquard_oauth::session::ClientSessionData<'static>) -> Self { 190 AuthSession::OAuth(session) 191 } 192 }
+1 -1
crates/jacquard/src/client/at_client.rs
··· 13 14 use jacquard_common::types::xrpc::{XrpcRequest, build_http_request}; 15 16 - use crate::client::{AtpSession, AuthSession, FileTokenStore, NSID_REFRESH_SESSION}; 17 18 /// Per-call overrides when sending via `AtClient`. 19 #[derive(Debug, Default, Clone)]
··· 13 14 use jacquard_common::types::xrpc::{XrpcRequest, build_http_request}; 15 16 + use crate::client::{AtpSession, AuthSession, NSID_REFRESH_SESSION}; 17 18 /// Per-call overrides when sending via `AtClient`. 19 #[derive(Debug, Default, Clone)]
+11 -10
crates/jacquard/src/identity.rs crates/jacquard-identity/src/lib.rs
··· 12 //! and optionally validate the document `id` against the requested DID. 13 14 // use crate::CowStr; // not currently needed directly here 15 16 use bytes::Bytes; 17 - use jacquard_common::IntoStatic; 18 use jacquard_common::error::TransportError; 19 use jacquard_common::http_client::HttpClient; 20 - use jacquard_common::ident_resolver::{ 21 - DidDocResponse, DidStep, HandleStep, IdentityError, IdentityResolver, MiniDoc, PlcSource, 22 - ResolverOptions, 23 - }; 24 use jacquard_common::types::xrpc::XrpcExt; 25 use percent_encoding::percent_decode_str; 26 use reqwest::StatusCode; 27 use url::{ParseError, Url}; 28 - 29 - use crate::api::com_atproto::identity::{resolve_did, resolve_handle::ResolveHandle}; 30 - use crate::types::did_doc::DidDocument; 31 - use crate::types::ident::AtIdentifier; 32 - use crate::types::string::{Did, Handle}; 33 34 #[cfg(feature = "dns")] 35 use hickory_resolver::{TokioAsyncResolver, config::ResolverConfig};
··· 12 //! and optionally validate the document `id` against the requested DID. 13 14 // use crate::CowStr; // not currently needed directly here 15 + pub mod resolver; 16 17 + use crate::resolver::{ 18 + DidDocResponse, DidStep, HandleStep, IdentityError, IdentityResolver, MiniDoc, PlcSource, 19 + ResolverOptions, 20 + }; 21 use bytes::Bytes; 22 + use jacquard_api::com_atproto::identity::resolve_did; 23 + use jacquard_api::com_atproto::identity::resolve_handle::ResolveHandle; 24 use jacquard_common::error::TransportError; 25 use jacquard_common::http_client::HttpClient; 26 + use jacquard_common::types::did::Did; 27 + use jacquard_common::types::did_doc::DidDocument; 28 + use jacquard_common::types::ident::AtIdentifier; 29 use jacquard_common::types::xrpc::XrpcExt; 30 + use jacquard_common::{IntoStatic, types::string::Handle}; 31 use percent_encoding::percent_decode_str; 32 use reqwest::StatusCode; 33 use url::{ParseError, Url}; 34 35 #[cfg(feature = "dns")] 36 use hickory_resolver::{TokioAsyncResolver, config::ResolverConfig};
+1 -2
crates/jacquard/src/lib.rs
··· 174 /// if enabled, reexport the attribute macros 175 pub use jacquard_derive::*; 176 177 - /// Identity resolution helpers (DIDs, handles, PDS endpoints) 178 - pub mod identity;
··· 174 /// if enabled, reexport the attribute macros 175 pub use jacquard_derive::*; 176 177 + pub use jacquard_identity as identity;
+1 -1
crates/jacquard/src/main.rs
··· 3 use jacquard::api::app_bsky::feed::get_timeline::GetTimeline; 4 use jacquard::api::com_atproto::server::create_session::CreateSession; 5 use jacquard::client::{AtpSession, AuthSession, BasicClient}; 6 - use jacquard::ident_resolver::IdentityResolver; 7 use jacquard::identity::slingshot_resolver_default; 8 use jacquard::types::string::Handle; 9 use miette::IntoDiagnostic;
··· 3 use jacquard::api::app_bsky::feed::get_timeline::GetTimeline; 4 use jacquard::api::com_atproto::server::create_session::CreateSession; 5 use jacquard::client::{AtpSession, AuthSession, BasicClient}; 6 + use jacquard::identity::resolver::IdentityResolver; 7 use jacquard::identity::slingshot_resolver_default; 8 use jacquard::types::string::Handle; 9 use miette::IntoDiagnostic;