Add documentation

This commit is contained in:
Wim Velzeboer
2026-09-23 21:45:20 +01:00
parent 5def68ef61
commit 3efcb85d6e
6 changed files with 429 additions and 81 deletions
+8 -3
View File
@@ -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
+31
View File
@@ -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
+81 -43
View File
@@ -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
View File
@@ -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);
+112
View File
@@ -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
View File
@@ -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};