atrium-oauth 0.1.2

Core library for implementing AT Protocol OAuth clients
Documentation
# ATrium OAuth: atproto flavoured OAuth client

Core library for implementing [atproto][ATPROTO] OAuth clients.

[ATPROTO]: https://atproto.com/ 'AT Protocol'

## Usage

### Configuration

```rust
use atrium_identity::{
    did::{CommonDidResolver, CommonDidResolverConfig, DEFAULT_PLC_DIRECTORY_URL},
    handle::{AtprotoHandleResolver, AtprotoHandleResolverConfig, DnsTxtResolver},
};
use atrium_oauth::{
    store::{session::MemorySessionStore, state::MemoryStateStore},
    AtprotoLocalhostClientMetadata, DefaultHttpClient, KnownScope, OAuthClient, OAuthClientConfig,
    OAuthResolverConfig, Scope,
};
use std::{error::Error, sync::Arc};

struct SomeDnsTxtResolver;

impl DnsTxtResolver for SomeDnsTxtResolver {
    async fn resolve(
        &self,
        _: &str,
    ) -> Result<Vec<String>, Box<dyn Error + Send + Sync + 'static>> {
        todo!()
    }
}

fn main() {
    let http_client = Arc::new(DefaultHttpClient::default());
    let config = OAuthClientConfig {
        client_metadata: AtprotoLocalhostClientMetadata {
            redirect_uris: Some(vec![String::from("http://127.0.0.1/callback")]),
            scopes: Some(vec![
                Scope::Known(KnownScope::Atproto),
                Scope::Known(KnownScope::TransitionGeneric),
            ]),
        },
        keys: None,
        resolver: OAuthResolverConfig {
            did_resolver: CommonDidResolver::new(CommonDidResolverConfig {
                plc_directory_url: DEFAULT_PLC_DIRECTORY_URL.to_string(),
                http_client: Arc::clone(&http_client),
            }),
            handle_resolver: AtprotoHandleResolver::new(AtprotoHandleResolverConfig {
                dns_txt_resolver: SomeDnsTxtResolver,
                http_client: Arc::clone(&http_client),
            }),
            authorization_server_metadata: Default::default(),
            protected_resource_metadata: Default::default(),
        },
        // A store for saving state data while the user is being redirected to the authorization server.
        state_store: MemoryStateStore::default(),
        // A store for saving session data.
        session_store: MemorySessionStore::default(),
    };
    let Ok(client) = OAuthClient::new(config) else {
        panic!("failed to create oauth client");
    };
}
```

### Authentication

```rust,ignore
use atrium_oauth::{AuthorizeOptions, KnownScope, OAuthClient, Scope};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = OAuthClient::new(...)?;
    let url = client
        .authorize(
            "foo.bsky.team",
            AuthorizeOptions {
                scopes: vec![
                    Scope::Known(KnownScope::Atproto),
                    Scope::Known(KnownScope::TransitionGeneric),
                ],
                ..Default::default()
            },
        )
        .await?;

    ...

    Ok(())
}
```

Make user visit `url`. Then, once it was redirected to the callback URI, perform the following:

```rust,ignore
use atrium_api::agent::Agent;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = OAuthClient::new(...)?;

    ...

    let query_params = "code=...&state=...";
    let params = serde_html_form::from_str(query_params)?;
    let (oauth_session, _) = client.callback(params).await?;

    ...

    Ok(())
}
```

The sign-in process results in an [`OAuthSession`] instance that can be used to make
authenticated requests to the resource server. This instance will automatically
refresh the credentials when needed.

### Making authenticated requests

The [`atrium_oauth`](crate) package provides a [`OAuthSession`] class that can be
used to make authenticated requests to Bluesky's AppView. This can be achieved
by constructing an [`Agent`](atrium_api::agent::Agent) instance using the
[`OAuthSession`] instance.

```rust,ignore
use atrium_api::agent::Agent;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {

    ...

    let (oauth_session, _) = client.callback(params).await?;
    let agent = Agent::new(oauth_session);
    let output = agent
        .api
        .app
        .bsky
        .feed
        .get_timeline(
            atrium_api::app::bsky::feed::get_timeline::ParametersData {
                algorithm: None,
                cursor: None,
                limit: 3.try_into().ok(),
            }
            .into(),
        )
        .await?;
    for feed in &output.feed {
        println!("{feed:?}");
    }

    ...

    Ok(())
}
```