+1
-1
README.md
+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
+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
+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
+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
+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
+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
+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