interactive intro to open social
at-me.zzstoatzz.io
1use atrium_identity::{
2 did::{CommonDidResolver, CommonDidResolverConfig, DEFAULT_PLC_DIRECTORY_URL},
3 handle::{AtprotoHandleResolver, AtprotoHandleResolverConfig, DnsTxtResolver},
4};
5use atrium_oauth::{
6 AtprotoClientMetadata, AtprotoLocalhostClientMetadata, AuthMethod, DefaultHttpClient,
7 GrantType, KnownScope, OAuthClient, OAuthClientConfig, OAuthResolverConfig, Scope,
8 store::{session::MemorySessionStore, state::MemoryStateStore},
9};
10use hickory_resolver::{TokioAsyncResolver, config::{ResolverConfig, ResolverOpts}};
11use std::sync::Arc;
12
13#[derive(Clone)]
14pub struct HickoryDnsResolver(Arc<TokioAsyncResolver>);
15
16impl DnsTxtResolver for HickoryDnsResolver {
17 async fn resolve(
18 &self,
19 domain: &str,
20 ) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
21 Ok(self
22 .0
23 .txt_lookup(domain)
24 .await?
25 .iter()
26 .map(|txt| txt.to_string())
27 .collect())
28 }
29}
30
31pub type OAuthClientType = Arc<
32 OAuthClient<
33 MemoryStateStore,
34 MemorySessionStore,
35 CommonDidResolver<DefaultHttpClient>,
36 AtprotoHandleResolver<HickoryDnsResolver, DefaultHttpClient>,
37 >,
38>;
39
40pub fn create_oauth_client() -> OAuthClientType {
41 let http_client = Arc::new(DefaultHttpClient::default());
42 let dns_resolver = HickoryDnsResolver(Arc::new(
43 TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default()),
44 ));
45
46 let redirect_uri = std::env::var("OAUTH_REDIRECT_URI")
47 .unwrap_or_else(|_| "http://127.0.0.1:8080/oauth/callback".to_string());
48
49 let is_production = redirect_uri.starts_with("https://");
50
51 let resolver = OAuthResolverConfig {
52 did_resolver: CommonDidResolver::new(CommonDidResolverConfig {
53 plc_directory_url: DEFAULT_PLC_DIRECTORY_URL.to_string(),
54 http_client: http_client.clone(),
55 }),
56 handle_resolver: AtprotoHandleResolver::new(AtprotoHandleResolverConfig {
57 dns_txt_resolver: dns_resolver,
58 http_client: http_client.clone(),
59 }),
60 authorization_server_metadata: Default::default(),
61 protected_resource_metadata: Default::default(),
62 };
63
64 if is_production {
65 let base_url = redirect_uri.trim_end_matches("/oauth/callback");
66 Arc::new(
67 OAuthClient::new(OAuthClientConfig {
68 client_metadata: AtprotoClientMetadata {
69 client_id: format!("{}/oauth-client-metadata.json", base_url),
70 client_uri: Some(base_url.to_string()),
71 redirect_uris: vec![redirect_uri],
72 token_endpoint_auth_method: AuthMethod::None,
73 grant_types: vec![GrantType::AuthorizationCode, GrantType::RefreshToken],
74 scopes: vec![Scope::Known(KnownScope::Atproto)],
75 jwks_uri: None,
76 token_endpoint_auth_signing_alg: None,
77 },
78 keys: None,
79 resolver,
80 state_store: MemoryStateStore::default(),
81 session_store: MemorySessionStore::default(),
82 })
83 .expect("failed to create oauth client"),
84 )
85 } else {
86 Arc::new(
87 OAuthClient::new(OAuthClientConfig {
88 client_metadata: AtprotoLocalhostClientMetadata {
89 redirect_uris: Some(vec![redirect_uri]),
90 scopes: Some(vec![Scope::Known(KnownScope::Atproto)]),
91 },
92 keys: None,
93 resolver,
94 state_store: MemoryStateStore::default(),
95 session_store: MemorySessionStore::default(),
96 })
97 .expect("failed to create oauth client"),
98 )
99 }
100}