diff --git a/README.md b/README.md index c0758cc..b734687 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A comprehensive collection of Rust crates for building AT Protocol applications. This workspace provides identity management, record operations, OAuth 2.0 flows, HTTP client operations, XRPC services, and event streaming capabilities. -Parts of this project were extracted from the open-source [smokesignal.events](https://tangled.sh/@smokesignal.events/smokesignal) project and are licensed under the MIT license. +**Note**: This project contains components extracted from the open-source [smokesignal.events](https://tangled.sh/@smokesignal.events/smokesignal) project, an AT Protocol event and RSVP management application. This library is released under the MIT license. ## Components diff --git a/crates/atproto-client/src/client.rs b/crates/atproto-client/src/client.rs index 9a7ffd8..4fd14d9 100644 --- a/crates/atproto-client/src/client.rs +++ b/crates/atproto-client/src/client.rs @@ -652,7 +652,6 @@ pub async fn get_apppassword_bytes_with_headers( })?) } - /// Performs an app password-authenticated HTTP POST request with JSON body and returns the response as bytes. /// /// This is useful when the server returns binary data such as images, CAR files, @@ -704,4 +703,4 @@ pub async fn post_apppassword_bytes_with_headers( url: url.to_string(), error, })?) -} \ No newline at end of file +} diff --git a/crates/atproto-oauth-aip/src/lib.rs b/crates/atproto-oauth-aip/src/lib.rs index d971034..4783cf3 100644 --- a/crates/atproto-oauth-aip/src/lib.rs +++ b/crates/atproto-oauth-aip/src/lib.rs @@ -1,4 +1,55 @@ -//! AT Protocol OAuth AIP implementation. +//! # AT Protocol OAuth AIP (Identity Provider) Implementation +//! +//! This crate provides a comprehensive OAuth 2.0 workflow implementation for AT Protocol +//! Identity Providers (AIPs). It handles the complete OAuth flow including Pushed +//! Authorization Requests (PAR), token exchange, and session management according to +//! AT Protocol specifications. +//! +//! ## Key Features +//! +//! - **OAuth 2.0 Authorization Code Flow**: Complete implementation with PKCE support +//! - **Pushed Authorization Requests (PAR)**: Enhanced security through server-side request storage +//! - **Token Exchange**: Secure token issuance and refresh capabilities +//! - **Session Management**: AT Protocol session establishment and validation +//! - **Resource Validation**: OAuth protected resource and authorization server validation +//! +//! ## Usage +//! +//! The primary entry point is the workflow module which provides functions for each +//! stage of the OAuth flow: +//! +//! ```rust,no_run +//! use atproto_oauth_aip::workflow::{oauth_init, oauth_complete, session_exchange}; +//! use atproto_oauth_aip::resources::{oauth_protected_resource, oauth_authorization_server}; +//! +//! // Initialize OAuth flow with PAR +//! let auth_url = oauth_init( +//! &oauth_client, +//! &authorization_server, +//! "user_handle", +//! "https://redirect.example.com/callback" +//! ).await?; +//! +//! // Complete OAuth flow with authorization code +//! let token_response = oauth_complete( +//! &oauth_client, +//! &authorization_server, +//! &oauth_request, +//! "authorization_code" +//! ).await?; +//! +//! // Exchange tokens for AT Protocol session +//! let session = session_exchange( +//! &protected_resource, +//! &token_response.access_token, +//! &dpop_key +//! ).await?; +//! ``` +//! +//! ## Error Handling +//! +//! All operations use structured error types with descriptive messages following +//! the project's error convention format. #![warn(missing_docs)] /// Error types for OAuth workflow operations. diff --git a/crates/atproto-oauth-aip/src/workflow.rs b/crates/atproto-oauth-aip/src/workflow.rs index c0b7942..bc6eb17 100644 --- a/crates/atproto-oauth-aip/src/workflow.rs +++ b/crates/atproto-oauth-aip/src/workflow.rs @@ -1,3 +1,71 @@ +//! # OAuth 2.0 Workflow Implementation for AT Protocol Identity Providers +//! +//! This module provides a complete OAuth 2.0 authorization code flow implementation +//! specifically designed for AT Protocol Identity Providers (AIPs). It handles the +//! three main phases of OAuth authentication: initialization, completion, and session exchange. +//! +//! ## Workflow Overview +//! +//! The OAuth workflow consists of three main functions that handle different phases: +//! +//! 1. **Initialization (`oauth_init`)**: Creates a Pushed Authorization Request (PAR) +//! and returns the authorization URL for user consent +//! 2. **Completion (`oauth_complete`)**: Exchanges the authorization code for access tokens +//! 3. **Session Exchange (`session_exchange`)**: Converts OAuth tokens to AT Protocol sessions +//! +//! ## Security Features +//! +//! - **Pushed Authorization Requests (PAR)**: Enhanced security by storing authorization +//! parameters server-side rather than in redirect URLs +//! - **PKCE (Proof Key for Code Exchange)**: Protection against authorization code +//! interception attacks +//! - **DPoP (Demonstration of Proof-of-Possession)**: Cryptographic binding of tokens +//! to specific keys for enhanced security +//! +//! ## Usage Example +//! +//! ```rust,no_run +//! use atproto_oauth_aip::workflow::{oauth_init, oauth_complete, session_exchange, OAuthClient}; +//! use atproto_oauth::resources::{AuthorizationServer, OAuthProtectedResource}; +//! +//! // 1. Initialize OAuth flow +//! let oauth_client = OAuthClient { +//! redirect_uri: "https://myapp.com/callback".to_string(), +//! client_id: "my_client_id".to_string(), +//! client_secret: "my_client_secret".to_string(), +//! }; +//! +//! let auth_url = oauth_init( +//! &oauth_client, +//! &authorization_server, +//! "user.bsky.social", +//! "https://myapp.com/callback" +//! ).await?; +//! +//! // User visits auth_url and grants consent, returns with authorization code +//! +//! // 2. Complete OAuth flow +//! let token_response = oauth_complete( +//! &oauth_client, +//! &authorization_server, +//! &oauth_request, +//! "received_auth_code" +//! ).await?; +//! +//! // 3. Exchange for AT Protocol session +//! let session = session_exchange( +//! &protected_resource, +//! &token_response.access_token, +//! &dpop_private_key +//! ).await?; +//! ``` +//! +//! ## Error Handling +//! +//! All functions return `Result` with detailed error information +//! for each phase of the OAuth flow including network failures, parsing errors, +//! and protocol violations. + use crate::errors::OAuthWorkflowError; use anyhow::Result; use atproto_oauth::{ diff --git a/crates/atproto-oauth-axum/src/bin/atproto-oauth-tool.rs b/crates/atproto-oauth-axum/src/bin/atproto-oauth-tool.rs index 0ea6d14..0124957 100644 --- a/crates/atproto-oauth-axum/src/bin/atproto-oauth-tool.rs +++ b/crates/atproto-oauth-axum/src/bin/atproto-oauth-tool.rs @@ -573,7 +573,10 @@ async fn handle_refresh_command( println!("Refresh Token: {}", &token_response.refresh_token); println!("Scope: {}", token_response.scope); println!("Expires In: {} seconds", token_response.expires_in); - println!("Subject: {}", token_response.sub.as_deref().unwrap_or("N/A")); + println!( + "Subject: {}", + token_response.sub.as_deref().unwrap_or("N/A") + ); println!("DPoP Key: {}", dpop_key); Ok(()) diff --git a/crates/atproto-oauth-axum/src/state.rs b/crates/atproto-oauth-axum/src/state.rs index 02fb5ff..8208b9a 100644 --- a/crates/atproto-oauth-axum/src/state.rs +++ b/crates/atproto-oauth-axum/src/state.rs @@ -39,7 +39,9 @@ pub struct OAuthClientConfig { impl OAuthClientConfig { /// Returns the OAuth scope, using the default "atproto transition:generic" if not set. pub fn scope(&self) -> &str { - self.scope.as_deref().unwrap_or("atproto transition:generic") + self.scope + .as_deref() + .unwrap_or("atproto transition:generic") } } diff --git a/crates/atproto-oauth/src/dpop.rs b/crates/atproto-oauth/src/dpop.rs index 574a584..230c1d4 100644 --- a/crates/atproto-oauth/src/dpop.rs +++ b/crates/atproto-oauth/src/dpop.rs @@ -1362,14 +1362,16 @@ mod tests { use atproto_identity::key::{KeyType, generate_key}; let key_data = generate_key(KeyType::P256Private)?; - + // Create a DPoP token with a nonce by manually building it let (_, header, mut claims) = auth_dpop(&key_data, "POST", "https://example.com/token")?; - + // Add nonce to claims let test_nonce = "test_nonce_12345"; - claims.private.insert("nonce".to_string(), test_nonce.into()); - + claims + .private + .insert("nonce".to_string(), test_nonce.into()); + // Create the token with nonce let dpop_token = mint(&key_data, &header, &claims)?; @@ -1389,14 +1391,16 @@ mod tests { use atproto_identity::key::{KeyType, generate_key}; let key_data = generate_key(KeyType::P256Private)?; - - // Create a DPoP token with a specific nonce + + // Create a DPoP token with a specific nonce let (_, header, mut claims) = auth_dpop(&key_data, "POST", "https://example.com/token")?; - + // Add a nonce that won't match the expected values let token_nonce = "token_nonce_that_wont_match"; - claims.private.insert("nonce".to_string(), token_nonce.into()); - + claims + .private + .insert("nonce".to_string(), token_nonce.into()); + // Create the token with nonce let dpop_token = mint(&key_data, &header, &claims)?;