diff --git a/src/auth/discover.rs b/src/auth/discover.rs index bc77d83..adcae8d 100644 --- a/src/auth/discover.rs +++ b/src/auth/discover.rs @@ -1,5 +1,6 @@ -use hickory_resolver::{TokioResolver}; +use hickory_resolver::TokioResolver; use serde::Deserialize; +use tower_cookies::cookie::time::error::Format; // const ( // authServerUrl = "/.well-known/oauth-protected-resource" @@ -9,12 +10,12 @@ use serde::Deserialize; // plcDirectoryUrl = "https://plc.directory/" // atprotopds = "AtprotoPersonalDataServer" // ) -const AUTH_SERVER_URL: &str = "/.well-known/oauth-protected-resource"; -const AS_META_URL: &str = "/.well-known/oauth-authorization-server"; -const DID_BY_HTTP_URL: &str = "/.well-known/atproto-did"; -const DNS_TXT_PREFIX: &str = "_atproto."; -const PLC_DIRECTORY_URL: &str = "https://plc.directory/"; -const ATPROTO_PDS: &str = "AtprotoPersonalDataServer"; +const AUTH_SERVER_URL: &str = "/.well-known/oauth-protected-resource"; +const AS_META_URL: &str = "/.well-known/oauth-authorization-server"; +const DID_BY_HTTP_URL: &str = "/.well-known/atproto-did"; +const DNS_TXT_PREFIX: &str = "_atproto."; +const PLC_DIRECTORY_URL: &str = "https://plc.directory/"; +const ATPROTO_PDS: &str = "AtprotoPersonalDataServer"; // type Service struct { // ID string `json:"id"` @@ -23,14 +24,13 @@ const ATPROTO_PDS: &str = "AtprotoPersonalDataServer"; // } #[derive(Deserialize)] struct Service { - id: String, - // this is like the json.marshall + id: String, + // this is like the json.marshall + #[serde(rename = "type")] + kind: String, - #[serde(rename="type")] - kind: String, - - #[serde(rename="serviceEndpoint")] - service_endpoint: String, + #[serde(rename = "serviceEndpoint")] + service_endpoint: String, } // type DIDDoc struct { @@ -38,7 +38,7 @@ struct Service { // } #[derive(Deserialize)] struct DIDDoc { - service: Vec + service: Vec, } // type AuthServerMeta struct { @@ -49,53 +49,67 @@ struct DIDDoc { // ScopesSupported []string `json:"scopes_supported"` // to verify the server supports atproto // DPopSigningAlgValuesSupported []string `json:"dpop_signing_alg_values_supported"` // to confirm ES256 is support before we send a DPoP proof // } -#[derive(Debug)] -#[derive(Deserialize)] +#[derive(Debug, Deserialize)] struct AuthServerMeta { - issuer: String, - authorization_endpoint: String, - token_endpoint: String, - pushed_authorization_request_endpoint: String, - scopes_supported: Vec, - dpop_signing_alg_values_supported: Vec + issuer: String, + authorization_endpoint: String, + token_endpoint: String, + pushed_authorization_request_endpoint: String, + scopes_supported: Vec, + dpop_signing_alg_values_supported: Vec, } #[derive(Debug)] pub struct DiscoveredSession { - did: String, - pds_url: String, - auth_meta: AuthServerMeta + did: String, + pds_url: String, + auth_meta: AuthServerMeta, } - // &str is a borrowed string reference, we're not writing to it pub async fn discover_from_handle(handle: &str) -> Result { - let did = resolve_handle_to_did(handle).await - .map_err(|e| anyhow::anyhow!("handle resolution failed: {}", e))?; + let did = resolve_handle_to_did(handle) + .await + .map_err(|e| anyhow::anyhow!("handle resolution failed: {}", e))?; - println!("resolved did: {}", did); - return Ok(DiscoveredSession{ - did, - pds_url: todo!(), - auth_meta: todo!(), - }) + println!("resolved did: {}", did); + return Ok(DiscoveredSession { + did, + pds_url: todo!(), + auth_meta: todo!(), + }); } async fn resolve_handle_to_did(handle: &str) -> Result { - let lookup = format!("{}{}", DNS_TXT_PREFIX, handle); + // dns resolution + let lookup = format!("{}{}.", DNS_TXT_PREFIX, handle); + + let resolver = TokioResolver::builder_tokio()?.build(); + if let Ok(records) = resolver.txt_lookup(lookup).await { + for record in records { + let record_str = record.to_string(); + if let Some(did) = record_str.strip_prefix("did=") { + if !did.starts_with("did:") { + return Err(anyhow::anyhow!("invalid DID in DNS record: {}", did)); + } + return Ok(did.to_string()); + } + } + } - let resolver = TokioResolver::builder_tokio()?.build(); - let records = resolver.txt_lookup(lookup).await?; + // http falback + let d = format!("https://{}{}", handle, DID_BY_HTTP_URL); + let client = reqwest::Client::new(); + let response = client.get(&d).send().await; - for record in records { - let record_str = record.to_string(); - if let Some(did) = record_str.strip_prefix("did=") { - if !did.starts_with("did:") { - return Err(anyhow::anyhow!("invalid DID in DNS record: {}", did)); - } - return Ok(did.to_string()); + if let Ok(resp) = response { + if resp.status().is_success() { + let did = resp.text().await?.trim().to_string(); + if did.starts_with("did:") { + return Ok(did); + } + } } - } - return Err(anyhow::anyhow!("no DID found in DNS records")); + return Err(anyhow::anyhow!("no DID found in DNS records")); }