Skip to main content

cirrus_auth/
static_token.rs

1//! Preset-credential auth: caller supplies a known-good access token and
2//! instance URL. No refresh, no negotiation.
3//!
4//! Useful for tests, short-lived scripts, or callers that have already
5//! performed an OAuth flow out-of-band (e.g. via the `sfdx` CLI) and want to
6//! reuse the resulting token. Real OAuth flows live in sibling modules.
7
8use crate::AuthSession;
9use crate::error::AuthResult;
10use async_trait::async_trait;
11use std::borrow::Cow;
12
13/// Authentication backed by a fixed access token and instance URL.
14///
15/// Once the token expires, requests will start failing with a 401
16/// `INVALID_SESSION_ID` from Salesforce. This type does not refresh;
17/// pair it with a flow that does (or rebuild the client) to recover.
18#[derive(Debug, Clone)]
19pub struct StaticTokenAuth {
20    access_token: String,
21    instance_url: String,
22}
23
24impl StaticTokenAuth {
25    /// Constructs a session from a known token and instance URL.
26    ///
27    /// `instance_url` is normalized by trimming a trailing slash so that
28    /// path concatenation in the client always produces clean URLs.
29    ///
30    /// Static-token auth doesn't refresh — when the token expires, calls
31    /// will surface 401. Use for short-lived scripts, CLI tools, or
32    /// tests where you've pasted a token from `sf org display`. For
33    /// long-running services prefer [`JwtAuth`](crate::JwtAuth) or
34    /// [`RefreshTokenAuth`](crate::RefreshTokenAuth).
35    ///
36    /// # Example
37    ///
38    /// ```
39    /// use cirrus_auth::{AuthSession, StaticTokenAuth};
40    /// use std::sync::Arc;
41    ///
42    /// let auth: Arc<dyn AuthSession> = Arc::new(StaticTokenAuth::new(
43    ///     "00D...!AQ...",
44    ///     "https://my-org.my.salesforce.com",
45    /// ));
46    /// assert_eq!(auth.instance_url(), "https://my-org.my.salesforce.com");
47    /// ```
48    pub fn new(access_token: impl Into<String>, instance_url: impl Into<String>) -> Self {
49        let mut instance_url: String = instance_url.into();
50        if instance_url.ends_with('/') {
51            instance_url.pop();
52        }
53        Self {
54            access_token: access_token.into(),
55            instance_url,
56        }
57    }
58}
59
60#[async_trait]
61impl AuthSession for StaticTokenAuth {
62    async fn access_token(&self) -> AuthResult<Cow<'_, str>> {
63        Ok(Cow::Borrowed(&self.access_token))
64    }
65
66    fn instance_url(&self) -> &str {
67        &self.instance_url
68    }
69}
70
71#[cfg(test)]
72#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
73mod tests {
74    use super::*;
75
76    #[tokio::test]
77    async fn returns_provided_token_and_url() {
78        let auth = StaticTokenAuth::new("tok", "https://example.my.salesforce.com");
79        assert_eq!(auth.access_token().await.unwrap(), "tok");
80        assert_eq!(auth.instance_url(), "https://example.my.salesforce.com");
81    }
82
83    #[tokio::test]
84    async fn strips_trailing_slash_from_instance_url() {
85        let auth = StaticTokenAuth::new("tok", "https://example.my.salesforce.com/");
86        assert_eq!(auth.instance_url(), "https://example.my.salesforce.com");
87    }
88}