A library for ATProtocol identities.

chore: 0.8.0 release prep

Signed-off-by: Nick Gerakines <nick.gerakines@gmail.com>

Changed files
+142 -15
crates
atproto-client
src
atproto-oauth
src
atproto-oauth-aip
atproto-oauth-axum
+1 -1
README.md
··· 2 2 3 3 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. 4 4 5 - 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. 5 + **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. 6 6 7 7 ## Components 8 8
+1 -2
crates/atproto-client/src/client.rs
··· 652 652 })?) 653 653 } 654 654 655 - 656 655 /// Performs an app password-authenticated HTTP POST request with JSON body and returns the response as bytes. 657 656 /// 658 657 /// This is useful when the server returns binary data such as images, CAR files, ··· 704 703 url: url.to_string(), 705 704 error, 706 705 })?) 707 - } 706 + }
+52 -1
crates/atproto-oauth-aip/src/lib.rs
··· 1 - //! AT Protocol OAuth AIP implementation. 1 + //! # AT Protocol OAuth AIP (Identity Provider) Implementation 2 + //! 3 + //! This crate provides a comprehensive OAuth 2.0 workflow implementation for AT Protocol 4 + //! Identity Providers (AIPs). It handles the complete OAuth flow including Pushed 5 + //! Authorization Requests (PAR), token exchange, and session management according to 6 + //! AT Protocol specifications. 7 + //! 8 + //! ## Key Features 9 + //! 10 + //! - **OAuth 2.0 Authorization Code Flow**: Complete implementation with PKCE support 11 + //! - **Pushed Authorization Requests (PAR)**: Enhanced security through server-side request storage 12 + //! - **Token Exchange**: Secure token issuance and refresh capabilities 13 + //! - **Session Management**: AT Protocol session establishment and validation 14 + //! - **Resource Validation**: OAuth protected resource and authorization server validation 15 + //! 16 + //! ## Usage 17 + //! 18 + //! The primary entry point is the workflow module which provides functions for each 19 + //! stage of the OAuth flow: 20 + //! 21 + //! ```rust,no_run 22 + //! use atproto_oauth_aip::workflow::{oauth_init, oauth_complete, session_exchange}; 23 + //! use atproto_oauth_aip::resources::{oauth_protected_resource, oauth_authorization_server}; 24 + //! 25 + //! // Initialize OAuth flow with PAR 26 + //! let auth_url = oauth_init( 27 + //! &oauth_client, 28 + //! &authorization_server, 29 + //! "user_handle", 30 + //! "https://redirect.example.com/callback" 31 + //! ).await?; 32 + //! 33 + //! // Complete OAuth flow with authorization code 34 + //! let token_response = oauth_complete( 35 + //! &oauth_client, 36 + //! &authorization_server, 37 + //! &oauth_request, 38 + //! "authorization_code" 39 + //! ).await?; 40 + //! 41 + //! // Exchange tokens for AT Protocol session 42 + //! let session = session_exchange( 43 + //! &protected_resource, 44 + //! &token_response.access_token, 45 + //! &dpop_key 46 + //! ).await?; 47 + //! ``` 48 + //! 49 + //! ## Error Handling 50 + //! 51 + //! All operations use structured error types with descriptive messages following 52 + //! the project's error convention format. 2 53 #![warn(missing_docs)] 3 54 4 55 /// Error types for OAuth workflow operations.
+68
crates/atproto-oauth-aip/src/workflow.rs
··· 1 + //! # OAuth 2.0 Workflow Implementation for AT Protocol Identity Providers 2 + //! 3 + //! This module provides a complete OAuth 2.0 authorization code flow implementation 4 + //! specifically designed for AT Protocol Identity Providers (AIPs). It handles the 5 + //! three main phases of OAuth authentication: initialization, completion, and session exchange. 6 + //! 7 + //! ## Workflow Overview 8 + //! 9 + //! The OAuth workflow consists of three main functions that handle different phases: 10 + //! 11 + //! 1. **Initialization (`oauth_init`)**: Creates a Pushed Authorization Request (PAR) 12 + //! and returns the authorization URL for user consent 13 + //! 2. **Completion (`oauth_complete`)**: Exchanges the authorization code for access tokens 14 + //! 3. **Session Exchange (`session_exchange`)**: Converts OAuth tokens to AT Protocol sessions 15 + //! 16 + //! ## Security Features 17 + //! 18 + //! - **Pushed Authorization Requests (PAR)**: Enhanced security by storing authorization 19 + //! parameters server-side rather than in redirect URLs 20 + //! - **PKCE (Proof Key for Code Exchange)**: Protection against authorization code 21 + //! interception attacks 22 + //! - **DPoP (Demonstration of Proof-of-Possession)**: Cryptographic binding of tokens 23 + //! to specific keys for enhanced security 24 + //! 25 + //! ## Usage Example 26 + //! 27 + //! ```rust,no_run 28 + //! use atproto_oauth_aip::workflow::{oauth_init, oauth_complete, session_exchange, OAuthClient}; 29 + //! use atproto_oauth::resources::{AuthorizationServer, OAuthProtectedResource}; 30 + //! 31 + //! // 1. Initialize OAuth flow 32 + //! let oauth_client = OAuthClient { 33 + //! redirect_uri: "https://myapp.com/callback".to_string(), 34 + //! client_id: "my_client_id".to_string(), 35 + //! client_secret: "my_client_secret".to_string(), 36 + //! }; 37 + //! 38 + //! let auth_url = oauth_init( 39 + //! &oauth_client, 40 + //! &authorization_server, 41 + //! "user.bsky.social", 42 + //! "https://myapp.com/callback" 43 + //! ).await?; 44 + //! 45 + //! // User visits auth_url and grants consent, returns with authorization code 46 + //! 47 + //! // 2. Complete OAuth flow 48 + //! let token_response = oauth_complete( 49 + //! &oauth_client, 50 + //! &authorization_server, 51 + //! &oauth_request, 52 + //! "received_auth_code" 53 + //! ).await?; 54 + //! 55 + //! // 3. Exchange for AT Protocol session 56 + //! let session = session_exchange( 57 + //! &protected_resource, 58 + //! &token_response.access_token, 59 + //! &dpop_private_key 60 + //! ).await?; 61 + //! ``` 62 + //! 63 + //! ## Error Handling 64 + //! 65 + //! All functions return `Result<T, OAuthWorkflowError>` with detailed error information 66 + //! for each phase of the OAuth flow including network failures, parsing errors, 67 + //! and protocol violations. 68 + 1 69 use crate::errors::OAuthWorkflowError; 2 70 use anyhow::Result; 3 71 use atproto_oauth::{
+4 -1
crates/atproto-oauth-axum/src/bin/atproto-oauth-tool.rs
··· 573 573 println!("Refresh Token: {}", &token_response.refresh_token); 574 574 println!("Scope: {}", token_response.scope); 575 575 println!("Expires In: {} seconds", token_response.expires_in); 576 - println!("Subject: {}", token_response.sub.as_deref().unwrap_or("N/A")); 576 + println!( 577 + "Subject: {}", 578 + token_response.sub.as_deref().unwrap_or("N/A") 579 + ); 577 580 println!("DPoP Key: {}", dpop_key); 578 581 579 582 Ok(())
+3 -1
crates/atproto-oauth-axum/src/state.rs
··· 39 39 impl OAuthClientConfig { 40 40 /// Returns the OAuth scope, using the default "atproto transition:generic" if not set. 41 41 pub fn scope(&self) -> &str { 42 - self.scope.as_deref().unwrap_or("atproto transition:generic") 42 + self.scope 43 + .as_deref() 44 + .unwrap_or("atproto transition:generic") 43 45 } 44 46 } 45 47
+13 -9
crates/atproto-oauth/src/dpop.rs
··· 1362 1362 use atproto_identity::key::{KeyType, generate_key}; 1363 1363 1364 1364 let key_data = generate_key(KeyType::P256Private)?; 1365 - 1365 + 1366 1366 // Create a DPoP token with a nonce by manually building it 1367 1367 let (_, header, mut claims) = auth_dpop(&key_data, "POST", "https://example.com/token")?; 1368 - 1368 + 1369 1369 // Add nonce to claims 1370 1370 let test_nonce = "test_nonce_12345"; 1371 - claims.private.insert("nonce".to_string(), test_nonce.into()); 1372 - 1371 + claims 1372 + .private 1373 + .insert("nonce".to_string(), test_nonce.into()); 1374 + 1373 1375 // Create the token with nonce 1374 1376 let dpop_token = mint(&key_data, &header, &claims)?; 1375 1377 ··· 1389 1391 use atproto_identity::key::{KeyType, generate_key}; 1390 1392 1391 1393 let key_data = generate_key(KeyType::P256Private)?; 1392 - 1393 - // Create a DPoP token with a specific nonce 1394 + 1395 + // Create a DPoP token with a specific nonce 1394 1396 let (_, header, mut claims) = auth_dpop(&key_data, "POST", "https://example.com/token")?; 1395 - 1397 + 1396 1398 // Add a nonce that won't match the expected values 1397 1399 let token_nonce = "token_nonce_that_wont_match"; 1398 - claims.private.insert("nonce".to_string(), token_nonce.into()); 1399 - 1400 + claims 1401 + .private 1402 + .insert("nonce".to_string(), token_nonce.into()); 1403 + 1400 1404 // Create the token with nonce 1401 1405 let dpop_token = mint(&key_data, &header, &claims)?; 1402 1406