+11
-4
crates/atproto-oauth-aip/src/errors.rs
+11
-4
crates/atproto-oauth-aip/src/errors.rs
···
6
//!
7
//! ## Error Categories
8
//!
9
-
//! - **`OAuthWorkflowError`** (workflow-1 to workflow-8): OAuth workflow operations including PAR, token exchange, and session management
10
//!
11
//! ## Error Format
12
//!
···
44
#[error("error-atproto-oauth-aip-workflow-5 Token response json parsing failed: {0}")]
45
TokenResponseParseFailed(#[source] reqwest::Error),
46
47
/// Failed to send session exchange HTTP request.
48
-
#[error("error-atproto-oauth-aip-workflow-6 Session request failed: {0}")]
49
SessionRequestFailed(#[source] reqwest::Error),
50
51
/// Failed to parse session response JSON.
52
-
#[error("error-atproto-oauth-aip-workflow-7 Session json parsing failed: {0}")]
53
SessionResponseParseFailed(#[source] reqwest::Error),
54
55
/// Session response contained an error.
56
-
#[error("error-atproto-oauth-aip-workflow-8 Session response invalid: {message}")]
57
SessionResponseInvalid {
58
/// Error message from the session response.
59
message: String,
···
6
//!
7
//! ## Error Categories
8
//!
9
+
//! - **`OAuthWorkflowError`** (workflow-1 to workflow-9): OAuth workflow operations including PAR, token exchange, and session management
10
//!
11
//! ## Error Format
12
//!
···
44
#[error("error-atproto-oauth-aip-workflow-5 Token response json parsing failed: {0}")]
45
TokenResponseParseFailed(#[source] reqwest::Error),
46
47
+
/// Token response contained an error.
48
+
#[error("error-atproto-oauth-aip-workflow-6 Token response invalid: {message}")]
49
+
TokenResponseInvalid {
50
+
/// Error message from the token response.
51
+
message: String,
52
+
},
53
+
54
/// Failed to send session exchange HTTP request.
55
+
#[error("error-atproto-oauth-aip-workflow-7 Session request failed: {0}")]
56
SessionRequestFailed(#[source] reqwest::Error),
57
58
/// Failed to parse session response JSON.
59
+
#[error("error-atproto-oauth-aip-workflow-8 Session json parsing failed: {0}")]
60
SessionResponseParseFailed(#[source] reqwest::Error),
61
62
/// Session response contained an error.
63
+
#[error("error-atproto-oauth-aip-workflow-9 Session response invalid: {message}")]
64
SessionResponseInvalid {
65
/// Error message from the session response.
66
message: String,
+83
crates/atproto-oauth-aip/src/workflow.rs
+83
crates/atproto-oauth-aip/src/workflow.rs
···
563
}
564
}
565
}
566
+
567
+
/// Obtains an access token using OAuth client credentials grant.
568
+
///
569
+
/// This function implements the OAuth 2.0 client credentials flow for obtaining
570
+
/// service-to-service access tokens. This is typically used when a service needs
571
+
/// to authenticate itself rather than acting on behalf of a user.
572
+
///
573
+
/// # Arguments
574
+
///
575
+
/// * `http_client` - The HTTP client to use for making requests
576
+
/// * `aip_hostname` - The hostname of the AT Protocol Identity Provider (AIP)
577
+
/// * `aip_client_id` - The client ID for authenticating with the AIP
578
+
/// * `aip_client_secret` - The client secret for authenticating with the AIP
579
+
///
580
+
/// # Returns
581
+
///
582
+
/// Returns a `TokenResponse` containing the access token and metadata,
583
+
/// or an error if the token request fails.
584
+
///
585
+
/// # Example
586
+
///
587
+
/// ```no_run
588
+
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
589
+
/// use atproto_oauth_aip::workflow::client_credentials_token;
590
+
/// use atproto_oauth::workflow::TokenResponse;
591
+
/// # let http_client = reqwest::Client::new();
592
+
/// # let aip_hostname = "auth.example.com";
593
+
/// # let client_id = "service-client-id";
594
+
/// # let client_secret = "service-client-secret";
595
+
///
596
+
/// let token = client_credentials_token(
597
+
/// &http_client,
598
+
/// aip_hostname,
599
+
/// client_id,
600
+
/// client_secret,
601
+
/// ).await?;
602
+
///
603
+
/// println!("Access token: {}", token.access_token);
604
+
/// println!("Token type: {}", token.token_type);
605
+
/// println!("Expires in: {} seconds", token.expires_in);
606
+
/// # Ok(())
607
+
/// # }
608
+
/// ```
609
+
pub async fn client_credentials_token(
610
+
http_client: &reqwest::Client,
611
+
aip_hostname: &str,
612
+
aip_client_id: &str,
613
+
aip_client_secret: &str,
614
+
) -> Result<atproto_oauth::workflow::TokenResponse> {
615
+
// Construct the token endpoint URL
616
+
let token_url = format!("https://{}/oauth/token", aip_hostname);
617
+
618
+
// Prepare the form data for client credentials grant
619
+
let params = [("grant_type", "client_credentials")];
620
+
621
+
// Send the request with Basic authentication
622
+
let response = http_client
623
+
.post(&token_url)
624
+
.basic_auth(aip_client_id, Some(aip_client_secret))
625
+
.form(¶ms)
626
+
.send()
627
+
.await
628
+
.map_err(OAuthWorkflowError::TokenRequestFailed)?;
629
+
630
+
// Check if the request was successful
631
+
if !response.status().is_success() {
632
+
let status = response.status();
633
+
let error_text = response
634
+
.text()
635
+
.await
636
+
.unwrap_or_else(|_| "Unknown error".to_string());
637
+
return Err(OAuthWorkflowError::TokenResponseInvalid {
638
+
message: format!("Token request failed with status {}: {}", status, error_text),
639
+
}
640
+
.into());
641
+
}
642
+
643
+
// Parse the response
644
+
response
645
+
.json::<atproto_oauth::workflow::TokenResponse>()
646
+
.await
647
+
.map_err(|e| OAuthWorkflowError::TokenResponseParseFailed(e).into())
648
+
}