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, client_id: impl Into, client_secret: impl Into, ) -> 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 { 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::(&body) .map_err(|e| SalesforceAuthError::TokenExchange(e.to_string()))?) } }