Files
rustsf_auth/src/credentials/client_credentials.rs
T
2026-09-22 18:05:06 +01:00

116 lines
3.8 KiB
Rust

use log::trace;
use crate::credentials::{http_client, required, SalesforceAuthFlow, SalesforceCredentials};
use crate::salesforce_token_response::SalesforceTokenResponse;
use crate::SalesforceAuthError;
impl SalesforceCredentials {
/// Creates a configuration for the OAuth 2.0 client mod flow.
///
/// # Examples
///
/// ```rust
/// use rustsf_auth::{SalesforceCredentials, SalesforceAuthFlow};
///
/// let config = SalesforceCredentials::client_credentials(
/// "https://login.salesforce.com",
/// "client-id",
/// "client-secret",
/// );
///
/// assert_eq!(config.flow, SalesforceAuthFlow::ClientCredentials);
/// assert_eq!(config.client_id.as_deref(), Some("client-id"));
/// ```
pub fn client_credentials(
login_url: impl Into<String>,
client_id: impl Into<String>,
client_secret: impl Into<String>,
) -> Self {
Self {
flow: SalesforceAuthFlow::ClientCredentials,
login_url: Some(login_url.into()),
client_id: Some(client_id.into()),
client_secret: Some(client_secret.into()),
username: None,
private_key_pem: None,
access_token: None,
refresh_token: None,
instance_url: None,
}
}
/// Authenticates to Salesforce using the OAuth 2.0 client mod flow.
///
/// This method sends a client mod token request to:
///
/// `{login_url}/services/oauth2/token`
///
/// # Errors
///
/// Returns [`SalesforceAuthError`] if the OAuth client cannot be built, the HTTP
/// request fails, or Salesforce rejects the token request.
///
/// # Examples
///
/// ```rust,no_run
///
/// use rustsf_auth::authenticate_client_credentials;
/// # async fn example() -> Result<(), rustsf_auth::SalesforceAuthError> {
/// let session = authenticate_client_credentials(
/// "https://login.salesforce.com",
/// "client-id",
/// "client-secret",
/// ).await?;
///
/// println!("{}", session.access_token);
/// # Ok(())
/// # }
/// ```
///
/// Configure Salesforce
///
/// 1. Create the External Client App
/// - Navigate to Setup > External Client App Manager.
/// - Click New External Client App and enter the app name and contact email.
/// - Expand the API (Enable OAuth Settings) section:
/// - Check Enable OAuth.
/// - Check Enable Client Credentials Flow.
/// - Add the Manage user data via APIs (api) scope. Do not add refresh_token or offline_access as these are invalid for this flow.
/// - Click Create and note the Consumer Key (Client ID) and Consumer Secret.
///
/// 2. Configure Policies and Run As User
/// - In the External Client App Manager, find your new app and click Edit.
/// - Go to the Policies tab.
/// - Under OAuth Flows and External Client App Enhancements:
/// - Ensure Enable Client Credentials Flow is checked.
/// - In the Run As field, select the integration user (a dedicated service account with necessary API permissions).
/// - Save the changes.
///
pub(crate) async fn connect_client_credentials(&self) -> Result<SalesforceTokenResponse, SalesforceAuthError> {
let client_id = required(self.client_id.as_deref(), "client_id")?;
let client_secret = required(self.client_secret.as_deref(), "client_secret")?;
let url = self.token_url()?.url().clone();
let data = &[
("grant_type", "client_credentials"),
("client_id", client_id),
("client_secret", client_secret),
];
trace!("Client Credentials auth request: POST {}", url);
let response = http_client()?
.post(url)
.form(data)
.send()
.await?;
let status = response.status();
let body = response.text().await.map_err(|e| SalesforceAuthError::Http(e))?;
trace!("Client Credentials auth response: {}", body);
if !status.is_success() { return Err(SalesforceAuthError::OAuth2(body)) }
Ok(serde_json::from_str::<SalesforceTokenResponse>(&body)
.map_err(|e| SalesforceAuthError::TokenExchange(e.to_string()))?)
}
}