Add documentation
This commit is contained in:
@@ -8,7 +8,7 @@ Salesforce authentication SDK for Rust, supporting the OAuth2.0 flows:
|
||||
- [Client Credentials](#client-credentials)
|
||||
- JWT,
|
||||
- [sfdxAuthUrl](#sfdx-authentication-url),
|
||||
- Web Server,
|
||||
- [Web Server](#oauth-web-server-flow),
|
||||
(this flow waits for user interaction in a browser)
|
||||
|
||||
## Examples
|
||||
@@ -65,8 +65,6 @@ async fn main(){
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### SFDX Authentication URL
|
||||
Export the SFDX authentication URL using the [SFDX CLI](https://developer.salesforce.com/docs/atlas.en-us.sfdx_setup.meta/sfdx_setup/sfdx_setup_install_cli.htm), via the following command
|
||||
```bash
|
||||
@@ -125,3 +123,10 @@ Alternatively you could enter the sfdxAuthUrl directly as a string via:
|
||||
let config = SalesforceCredentials::sfdx_url("force://PlatformCLI::4sdf2348kS0Qqf3GEL....@example.my.salesforce.com").unwrap();
|
||||
let session = config.connect().await.unwrap();
|
||||
```
|
||||
|
||||
|
||||
### OAuth Web Server Flow
|
||||
With This authorization flow, users can authenticate themselves using a browser.
|
||||
The callback url is then invoked by Salesforce that sends a code with which the access_token and refresh token can be requested.
|
||||
|
||||
<<<<<<<<<<<<<<<<<<<<<<<<< Continue HERE
|
||||
@@ -1,3 +1,9 @@
|
||||
//! Access token authentication module.
|
||||
//!
|
||||
//! This module provides functionality for authenticating with Salesforce using an existing
|
||||
//! access token. It supports both simple access token authentication and refresh token
|
||||
//! capabilities when additional credentials are provided.
|
||||
|
||||
use crate::credentials::{required, SalesforceAuthFlow, SalesforceCredentials};
|
||||
use crate::error::SalesforceAuthError;
|
||||
use crate::salesforce_token_response::SalesforceTokenResponse;
|
||||
@@ -9,6 +15,15 @@ impl SalesforceCredentials {
|
||||
/// supplied, the resulting [`SalesforceAuthSession`] can later refresh its
|
||||
/// access token with [`SalesforceAuthSession::refresh_access_token`].
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `access_token` - The existing Salesforce access token
|
||||
/// * `instance_url` - The Salesforce instance URL (e.g., "https://example.my.salesforce.com")
|
||||
/// * `refresh_token` - Optional refresh token for renewing the access token
|
||||
/// * `client_id` - Optional client ID from the connected app
|
||||
/// * `client_secret` - Optional client secret from the connected app
|
||||
/// * `login_url` - Optional Salesforce login URL (e.g., "https://login.salesforce.com")
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
@@ -26,6 +41,11 @@ impl SalesforceCredentials {
|
||||
/// assert_eq!(config.flow, SalesforceAuthFlow::AccessToken);
|
||||
/// assert_eq!(config.access_token.as_deref(), Some("access-token"));
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
/// An access token has a limited lifetime and will expire, since there is no refresh_token,
|
||||
/// the application will panic when its expired.
|
||||
/// Only use this authentication flow for short-lived applications
|
||||
pub fn access_token(
|
||||
access_token: impl Into<String>,
|
||||
instance_url: impl Into<String>,
|
||||
@@ -52,6 +72,17 @@ impl SalesforceCredentials {
|
||||
/// This function does not validate the access token with Salesforce. It simply
|
||||
/// wraps the token and related metadata in a [`SalesforceAuthSession`].
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// This method uses the following fields from `self`:
|
||||
/// * `access_token` - The existing Salesforce access token (required)
|
||||
/// * `login_url` - The Salesforce login URL (required)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a `Result` containing a `SalesforceTokenResponse` on success, or a
|
||||
/// `SalesforceAuthError` if required fields are missing.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
|
||||
@@ -1,24 +1,72 @@
|
||||
//! OAuth 2.0 Client Credentials flow implementation for Salesforce authentication.
|
||||
//!
|
||||
//! This module provides functionality to authenticate with Salesforce using the
|
||||
//! OAuth 2.0 Client Credentials grant type, which is suitable for server-to-server
|
||||
//! integrations where no user interaction is required.
|
||||
|
||||
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.
|
||||
/// Connects to Salesforce using the OAuth 2.0 Client Credentials flow.
|
||||
///
|
||||
/// # Examples
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `login_url`: The Salesforce login url, e.g. `https://login.salesforce.com`
|
||||
/// - `client_id`: The Connected App Client Id
|
||||
/// - `client_secret`: The Connected App Client Secret
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rustsf_auth::{SalesforceCredentials, SalesforceAuthFlow};
|
||||
/// use oauth2::http::header::AUTHORIZATION;
|
||||
/// use oauth2::http::{HeaderMap, HeaderValue};
|
||||
/// use crate::credentials::SalesforceCredentials;
|
||||
///
|
||||
/// let config = SalesforceCredentials::client_credentials(
|
||||
/// "https://login.salesforce.com",
|
||||
/// "client-id",
|
||||
/// "client-secret",
|
||||
/// );
|
||||
/// pub const CONNECT_TIMEOUT: u64 = 15;
|
||||
/// pub const REQUEST_TIMEOUT: u64 = 30;
|
||||
/// #[tokio::main]
|
||||
/// async fn main(){
|
||||
/// // The client id and secret, (should never be hardcoded)
|
||||
/// let login_url = "https://example.my.salesforce.com/";
|
||||
/// let client_id = "client_id";
|
||||
/// let client_secret = "client_secret";
|
||||
///
|
||||
/// assert_eq!(config.flow, SalesforceAuthFlow::ClientCredentials);
|
||||
/// assert_eq!(config.client_id.as_deref(), Some("client-id"));
|
||||
/// // The Credentials configuration
|
||||
/// let config = SalesforceCredentials::client_credentials(
|
||||
/// login_url,
|
||||
/// client_id,
|
||||
/// client_secret);
|
||||
///
|
||||
/// // Constructing the authentication session and connecting to Salesforce
|
||||
/// let session = config.connect().await.unwrap();
|
||||
///
|
||||
/// // Build the headers to include the access token
|
||||
/// let mut headers = HeaderMap::new();
|
||||
/// let auth_value = format!("Bearer {}", session.access_token().await.unwrap());
|
||||
/// headers.insert(AUTHORIZATION, HeaderValue::from_str(&auth_value).unwrap());
|
||||
///
|
||||
/// // A REST API request
|
||||
/// let response = reqwest::Client::builder()
|
||||
/// .redirect(reqwest::redirect::Policy::none())
|
||||
/// .connect_timeout(std::time::Duration::from_secs(CONNECT_TIMEOUT))
|
||||
/// .timeout(std::time::Duration::from_secs(REQUEST_TIMEOUT))
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
/// .get(format!("{}/services/data", session.instance_url))
|
||||
/// .headers(headers)
|
||||
/// .send()
|
||||
/// .await
|
||||
/// .unwrap();
|
||||
///
|
||||
/// if response.status().is_success() {
|
||||
/// println!("SUCCESS Response: {:?}", response.text().await.unwrap());
|
||||
/// } else {
|
||||
/// println!("ERROR Response: {:?}", response.text().await.unwrap());
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn client_credentials(
|
||||
login_url: impl Into<String>,
|
||||
@@ -38,55 +86,45 @@ impl SalesforceCredentials {
|
||||
}
|
||||
}
|
||||
|
||||
/// Authenticates to Salesforce using the OAuth 2.0 client mod flow.
|
||||
/// Authenticates to Salesforce using the OAuth 2.0 Client Credentials flow.
|
||||
///
|
||||
/// This method sends a client mod token request to:
|
||||
/// This method sends a client credentials token request to:
|
||||
///
|
||||
/// `{login_url}/services/oauth2/token`
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// This method uses the following fields from `self`:
|
||||
/// - `client_id` - The Connected App Client ID (required)
|
||||
/// - `client_secret` - The Connected App Client Secret (required)
|
||||
/// - `login_url` - The Salesforce login URL (required)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`SalesforceAuthError`] if the OAuth client cannot be built, the HTTP
|
||||
/// request fails, or Salesforce rejects the token request.
|
||||
/// Returns [`SalesforceAuthError`] if:
|
||||
/// - Required fields (`client_id`, `client_secret`, or `login_url`) are missing
|
||||
/// - The HTTP request fails
|
||||
/// - Salesforce rejects the token request
|
||||
/// - The response cannot be parsed
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use rustsf_auth::{SalesforceCredentials, SalesforceAuthError};
|
||||
///
|
||||
/// use rustsf_auth::authenticate_client_credentials;
|
||||
/// # async fn example() -> Result<(), rustsf_auth::SalesforceAuthError> {
|
||||
/// let session = authenticate_client_credentials(
|
||||
/// # async fn example() -> Result<(), SalesforceAuthError> {
|
||||
/// let credentials = SalesforceCredentials::client_credentials(
|
||||
/// "https://login.salesforce.com",
|
||||
/// "client-id",
|
||||
/// "client-secret",
|
||||
/// ).await?;
|
||||
/// "your-client-id",
|
||||
/// "your-client-secret",
|
||||
/// );
|
||||
///
|
||||
/// println!("{}", session.access_token);
|
||||
/// let session = credentials.connect().await?;
|
||||
/// println!("Access token: {}", session.access_token().await?);
|
||||
/// # 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> {
|
||||
|
||||
pub 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")?;
|
||||
|
||||
|
||||
+187
-34
@@ -1,3 +1,9 @@
|
||||
//! Credentials and authentication flow management for Salesforce.
|
||||
//!
|
||||
//! This module provides the core types and methods for authenticating with Salesforce
|
||||
//! using various OAuth 2.0 flows, including client credentials, JWT bearer, SFDX auth URLs,
|
||||
//! and existing access tokens.
|
||||
|
||||
use std::sync::RwLock;
|
||||
use std::time::Duration;
|
||||
use log::trace;
|
||||
@@ -40,30 +46,16 @@ pub enum SalesforceAuthFlow {
|
||||
AccessToken,
|
||||
}
|
||||
|
||||
/// Configuration for authenticating to Salesforce.
|
||||
/// Credentials details for authenticating to Salesforce.
|
||||
///
|
||||
/// This struct provides a single, convenient way to pass mod and select
|
||||
/// the authentication flow. Prefer using the constructor methods such as
|
||||
/// [`SalesforceCredentials::client_credentials`], [`SalesforceCredentials::jwt_bearer`],
|
||||
/// [`SalesforceCredentials::sfdx_url`], and [`SalesforceCredentials::access_token`]
|
||||
/// This struct provides a single, convenient way to pass the required information for authentication
|
||||
///
|
||||
/// Prefer using one of the constructor methods below instead of manually constructing the struct.
|
||||
/// - [`SalesforceCredentials::client_credentials`],
|
||||
/// - [`SalesforceCredentials::jwt_bearer`],
|
||||
/// - [`SalesforceCredentials::sfdx_url`],
|
||||
/// - [`SalesforceCredentials::access_token`]
|
||||
/// instead of manually constructing the struct.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use rustsf_auth::{SalesforceCredentials, SalesforceAuthFlow};
|
||||
///
|
||||
/// let config = SalesforceCredentials::access_token(
|
||||
/// "access-token",
|
||||
/// "https://example.my.salesforce.com",
|
||||
/// None,
|
||||
/// None,
|
||||
/// None,
|
||||
/// None,
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(config.flow, SalesforceAuthFlow::AccessToken);
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SalesforceCredentials {
|
||||
/// The authentication flow to execute.
|
||||
@@ -114,20 +106,22 @@ impl SalesforceCredentials {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use rustsf_auth::SalesforceCredentials;
|
||||
/// use rustsf_auth::{SalesforceCredentials, SalesforceAuthError};
|
||||
///
|
||||
/// # async fn example() -> Result<(), rustsf_auth::SalesforceAuthError> {
|
||||
/// let config = SalesforceCredentials::client_credentials(
|
||||
/// "https://login.salesforce.com",
|
||||
/// "client-id",
|
||||
/// "client-secret",
|
||||
/// );
|
||||
///
|
||||
/// let session = config.connect().await?;
|
||||
/// println!("{}", session.access_token);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// async fn example() -> Result<(), SalesforceAuthError> {
|
||||
/// let config = SalesforceCredentials::client_credentials(
|
||||
/// "https://login.salesforce.com",
|
||||
/// "client-id",
|
||||
/// "client-secret",
|
||||
/// );
|
||||
/// let session = config.connect().await?;
|
||||
/// println!("{}", session.access_token().await?);
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
/// The OAuth2.0 Web Server flow cannot be used with this method as that type is a two-step flow.
|
||||
pub async fn connect(self) -> Result<SalesforceAuthSession, SalesforceAuthError> {
|
||||
let token_response = self.token_response_from_flow().await?;
|
||||
|
||||
@@ -160,11 +154,57 @@ impl SalesforceCredentials {
|
||||
})
|
||||
}
|
||||
|
||||
/// Reconnects to Salesforce by re-executing the configured authentication flow.
|
||||
///
|
||||
/// This internal method is used to obtain a fresh access token by running the
|
||||
/// authentication flow again from scratch.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`SalesforceAuthError`] if the authentication flow fails.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # use rustsf_auth::{SalesforceCredentials, SalesforceAuthError};
|
||||
/// # async fn example() -> Result<(), SalesforceAuthError> {
|
||||
/// let credentials = SalesforceCredentials::client_credentials(
|
||||
/// "https://login.salesforce.com",
|
||||
/// "client-id",
|
||||
/// "client-secret",
|
||||
/// );
|
||||
/// let token = credentials.reconnect().await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub(crate) async fn reconnect(&self) -> Result<SalesforceAuthToken, SalesforceAuthError> {
|
||||
let token_response = self.token_response_from_flow().await?;
|
||||
Ok(SalesforceAuthToken::from_token_response(token_response))
|
||||
}
|
||||
|
||||
/// Executes the configured authentication flow and returns the token response.
|
||||
///
|
||||
/// This internal method dispatches to the appropriate authentication method based
|
||||
/// on the configured [`SalesforceAuthFlow`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`SalesforceAuthError`] if the authentication flow fails.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # use rustsf_auth::{SalesforceCredentials, SalesforceAuthError};
|
||||
/// # async fn example() -> Result<(), SalesforceAuthError> {
|
||||
/// let credentials = SalesforceCredentials::client_credentials(
|
||||
/// "https://login.salesforce.com",
|
||||
/// "client-id",
|
||||
/// "client-secret",
|
||||
/// );
|
||||
/// let response = credentials.token_response_from_flow().await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
async fn token_response_from_flow(&self) -> Result<SalesforceTokenResponse, SalesforceAuthError> {
|
||||
match self.flow {
|
||||
SalesforceAuthFlow::AccessToken => self.connect_access_token(),
|
||||
@@ -174,6 +214,32 @@ impl SalesforceCredentials {
|
||||
}
|
||||
}
|
||||
|
||||
/// Refreshes the Salesforce access token using a refresh token or by reconnecting.
|
||||
///
|
||||
/// If a refresh token is available, this method uses it to obtain a new access token
|
||||
/// via the OAuth 2.0 refresh token grant. Otherwise, it falls back to reconnecting
|
||||
/// using the original authentication flow.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`SalesforceAuthError`] if the refresh token grant fails, if required
|
||||
/// fields are missing, or if the HTTP request fails.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # use rustsf_auth::{SalesforceCredentials, SalesforceAuthError};
|
||||
/// # async fn example() -> Result<(), SalesforceAuthError> {
|
||||
/// let credentials = SalesforceCredentials::client_credentials(
|
||||
/// "https://login.salesforce.com",
|
||||
/// "client-id",
|
||||
/// "client-secret",
|
||||
/// );
|
||||
/// let token = credentials.refresh().await?;
|
||||
/// println!("Access token: {}", token.access_token);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub(crate) async fn refresh(&self) -> Result<SalesforceAuthToken, SalesforceAuthError> {
|
||||
|
||||
match &self.refresh_token {
|
||||
@@ -209,6 +275,30 @@ impl SalesforceCredentials {
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs the OAuth 2.0 token endpoint URL from the login URL.
|
||||
///
|
||||
/// This method builds the full token URL by appending `/services/oauth2/token`
|
||||
/// to the configured login URL.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`SalesforceAuthError::MissingRequiredField`] if `login_url` is not set,
|
||||
/// or [`SalesforceAuthError::InvalidUrl`] if the constructed URL is invalid.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # use rustsf_auth::{SalesforceCredentials, SalesforceAuthError};
|
||||
/// # fn example() -> Result<(), SalesforceAuthError> {
|
||||
/// let credentials = SalesforceCredentials::client_credentials(
|
||||
/// "https://login.salesforce.com",
|
||||
/// "client-id",
|
||||
/// "client-secret",
|
||||
/// );
|
||||
/// let token_url = credentials.token_url()?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn token_url(&self) -> Result<TokenUrl, SalesforceAuthError> {
|
||||
match &self.login_url {
|
||||
Some(login_url) => {
|
||||
@@ -222,6 +312,24 @@ impl SalesforceCredentials {
|
||||
}
|
||||
|
||||
|
||||
/// Creates a configured HTTP client for Salesforce API requests.
|
||||
///
|
||||
/// This function builds a [`reqwest::Client`] with no automatic redirects and a 30-second timeout.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`SalesforceAuthError`] if the HTTP client cannot be built.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # use rustsf_auth::{SalesforceAuthError};
|
||||
/// # use rustsf_auth::credentials::http_client;
|
||||
/// # fn example() -> Result<(), SalesforceAuthError> {
|
||||
/// let client = http_client()?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub(crate) fn http_client() -> Result<reqwest::Client, SalesforceAuthError> {
|
||||
Ok(Client::builder()
|
||||
.redirect(reqwest::redirect::Policy::none())
|
||||
@@ -230,6 +338,29 @@ pub(crate) fn http_client() -> Result<reqwest::Client, SalesforceAuthError> {
|
||||
}
|
||||
|
||||
|
||||
/// Validates that a required field is present.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `value` - The optional field value to check
|
||||
/// * `field_name` - The name of the field for error reporting
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`SalesforceAuthError::MissingRequiredField`] if `value` is `None`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # use rustsf_auth::SalesforceAuthError;
|
||||
/// # use rustsf_auth::credentials::required;
|
||||
/// # fn example() -> Result<(), SalesforceAuthError> {
|
||||
/// let client_id = Some("my-client-id");
|
||||
/// let validated = required(client_id.as_deref(), "client_id")?;
|
||||
/// assert_eq!(validated, "my-client-id");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn required<'a>(
|
||||
value: Option<&'a str>,
|
||||
field_name: &'static str,
|
||||
@@ -239,6 +370,28 @@ fn required<'a>(
|
||||
value.ok_or(SalesforceAuthError::MissingRequiredField(field_name))
|
||||
}
|
||||
|
||||
/// Parses the organization ID and user ID from a Salesforce identity URL.
|
||||
///
|
||||
/// Salesforce identity URLs have the format:
|
||||
/// `https://login.salesforce.com/id/{org_id}/{user_id}`
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id_url` - The optional Salesforce identity URL to parse
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A tuple of `(org_id, user_id)`, where both are `None` if the URL is invalid or missing.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # use rustsf_auth::credentials::parse_salesforce_identity_ids;
|
||||
/// let url = Some("https://login.salesforce.com/id/00Dxx0000001gPLEAY/005xx000001SwiUAAS");
|
||||
/// let (org_id, user_id) = parse_salesforce_identity_ids(url);
|
||||
/// assert_eq!(org_id, Some("00Dxx0000001gPLEAY".to_string()));
|
||||
/// assert_eq!(user_id, Some("005xx000001SwiUAAS".to_string()));
|
||||
/// ```
|
||||
fn parse_salesforce_identity_ids(id_url: Option<&str>) -> (Option<String>, Option<String>) {
|
||||
let Some(id_url) = id_url else {
|
||||
return (None, None);
|
||||
|
||||
@@ -49,6 +49,28 @@ impl OAuthWebService {
|
||||
/// The caller should call [`OAuthWebService::authorization_url`] first, open
|
||||
/// the returned URL in a browser, then call [`OAuthWebService::connect`] to
|
||||
/// wait for the callback and receive a [`SalesforceAuthSession`].
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `login_url`: Salesforce login URL (e.g., `https://login.salesforce.com`)
|
||||
/// - `client_id`: Connected app client ID
|
||||
/// - `client_secret`: Optional connected app client secret
|
||||
/// - `redirect_uri`: OAuth callback URL (e.g., `http://localhost:8080/callback`)
|
||||
/// - `scopes`: Optional list of OAuth scopes. Defaults to `["api", "refresh_token", "offline_access"]`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use rustsf_auth::credentials::web_server::OAuthWebService;
|
||||
///
|
||||
/// let service = OAuthWebService::new(
|
||||
/// "https://login.salesforce.com",
|
||||
/// "my-client-id",
|
||||
/// Some("my-client-secret".to_string()),
|
||||
/// "http://localhost:8080/callback",
|
||||
/// None,
|
||||
/// );
|
||||
/// ```
|
||||
pub fn new(
|
||||
login_url: impl Into<String>,
|
||||
client_id: impl Into<String>,
|
||||
@@ -76,6 +98,34 @@ impl OAuthWebService {
|
||||
///
|
||||
/// This method does not open a browser. The caller is responsible for opening
|
||||
/// the returned URL or presenting it to the user.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the authorization URL that the user should visit to grant access.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`SalesforceAuthError::InvalidUrl`] if the login URL cannot be parsed.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use rustsf_auth::credentials::web_server::OAuthWebService;
|
||||
///
|
||||
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let service = OAuthWebService::new(
|
||||
/// "https://login.salesforce.com",
|
||||
/// "my-client-id",
|
||||
/// None,
|
||||
/// "http://localhost:8080/callback",
|
||||
/// None,
|
||||
/// );
|
||||
///
|
||||
/// let auth_url = service.authorization_url().await?;
|
||||
/// println!("Visit this URL: {}", auth_url);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn authorization_url(&self) -> Result<String, SalesforceAuthError> {
|
||||
let normalized = self.login_url.trim_end_matches('/');
|
||||
let authorize_url = format!("{normalized}/services/oauth2/authorize");
|
||||
@@ -102,6 +152,44 @@ impl OAuthWebService {
|
||||
///
|
||||
/// Call [`OAuthWebService::authorization_url`] first and open that URL in a
|
||||
/// browser before awaiting this method.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a [`SalesforceAuthSession`] containing the access token and other
|
||||
/// authentication details.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`SalesforceAuthError`] if:
|
||||
/// - The redirect URI is invalid or missing a port
|
||||
/// - The TCP listener cannot be bound
|
||||
/// - The OAuth callback is malformed
|
||||
/// - The state parameter doesn't match
|
||||
/// - Token exchange with Salesforce fails
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use rustsf_auth::credentials::web_server::OAuthWebService;
|
||||
///
|
||||
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let service = OAuthWebService::new(
|
||||
/// "https://login.salesforce.com",
|
||||
/// "my-client-id",
|
||||
/// Some("my-client-secret".to_string()),
|
||||
/// "http://localhost:8080/callback",
|
||||
/// None,
|
||||
/// );
|
||||
///
|
||||
/// let auth_url = service.authorization_url().await?;
|
||||
/// println!("Visit this URL: {}", auth_url);
|
||||
/// // User opens the URL in a browser and authorizes
|
||||
///
|
||||
/// let session = service.connect().await?;
|
||||
/// println!("Access token: {}", session.access_token().await?);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn connect(&self) -> Result<SalesforceAuthSession, SalesforceAuthError> {
|
||||
let callback_url = Url::parse(&self.redirect_uri)
|
||||
.map_err(|source| SalesforceAuthError::InvalidUrl {
|
||||
@@ -144,6 +232,30 @@ impl OAuthWebService {
|
||||
///
|
||||
/// If this method is not called, a default "Salesforce login complete" page is
|
||||
/// returned.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `response`: HTML content to display in the browser after successful authentication
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use rustsf_auth::credentials::web_server::OAuthWebService;
|
||||
///
|
||||
/// let custom_html = r#"<!doctype html>
|
||||
/// <html>
|
||||
/// <head><title>Success</title></head>
|
||||
/// <body><h1>Authentication successful!</h1></body>
|
||||
/// </html>"#;
|
||||
///
|
||||
/// let service = OAuthWebService::new(
|
||||
/// "https://login.salesforce.com",
|
||||
/// "my-client-id",
|
||||
/// None,
|
||||
/// "http://localhost:8080/callback",
|
||||
/// None,
|
||||
/// ).with_callback_response(custom_html);
|
||||
/// ```
|
||||
pub fn with_callback_response(mut self, response: impl Into<String>) -> Self {
|
||||
self.callback_response = Some(response.into());
|
||||
self
|
||||
|
||||
+10
-1
@@ -1,3 +1,12 @@
|
||||
//! # RustSF Auth
|
||||
//!
|
||||
//! Salesforce authentication SDK for Rust, supporting the OAuth2.0 flows:
|
||||
//! - [Client Credentials](crate::credentials::SalesforceCredentials#method.client_credentials)
|
||||
//! - JWT,
|
||||
//! - [sfdxAuthUrl](crate::credentials::SalesforceCredentials#method.sfdx_url),
|
||||
//! - [Web Server](crate::credentials::web_server::OAuthWebService#method.authorization_url),
|
||||
//! (this two-step flow lets a user authentiate via a browser)
|
||||
|
||||
use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
|
||||
pub mod credentials;
|
||||
@@ -5,7 +14,7 @@ pub mod error;
|
||||
pub(crate) mod salesforce_auth_token;
|
||||
pub mod salesforce_token_response;
|
||||
|
||||
use self::error::SalesforceAuthError;
|
||||
pub use self::error::SalesforceAuthError;
|
||||
use self::salesforce_auth_token::SalesforceAuthToken;
|
||||
|
||||
pub use self::credentials::{SalesforceAuthFlow, SalesforceCredentials};
|
||||
|
||||
Reference in New Issue
Block a user