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(Clone)]
19pub struct StaticTokenAuth {
20 access_token: String,
21 instance_url: String,
22}
23
24impl std::fmt::Debug for StaticTokenAuth {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 f.debug_struct("StaticTokenAuth")
27 .field("access_token", &"[redacted]")
28 .field("instance_url", &self.instance_url)
29 .finish()
30 }
31}
32
33impl StaticTokenAuth {
34 /// Constructs a session from a known token and instance URL.
35 ///
36 /// `instance_url` is normalized by trimming a trailing slash so that
37 /// path concatenation in the client always produces clean URLs.
38 ///
39 /// Static-token auth doesn't refresh — when the token expires, calls
40 /// will surface 401. Use for short-lived scripts, CLI tools, or
41 /// tests where you've pasted a token from `sf org display`. For
42 /// long-running services prefer [`JwtAuth`](crate::JwtAuth) or
43 /// [`RefreshTokenAuth`](crate::RefreshTokenAuth).
44 ///
45 /// # Example
46 ///
47 /// ```
48 /// use cirrus_auth::{AuthSession, StaticTokenAuth};
49 /// use std::sync::Arc;
50 ///
51 /// let auth: Arc<dyn AuthSession> = Arc::new(StaticTokenAuth::new(
52 /// "00D...!AQ...",
53 /// "https://my-org.my.salesforce.com",
54 /// ));
55 /// assert_eq!(auth.instance_url(), "https://my-org.my.salesforce.com");
56 /// ```
57 pub fn new(access_token: impl Into<String>, instance_url: impl Into<String>) -> Self {
58 let mut instance_url: String = instance_url.into();
59 if instance_url.ends_with('/') {
60 instance_url.pop();
61 }
62 Self {
63 access_token: access_token.into(),
64 instance_url,
65 }
66 }
67}
68
69#[async_trait]
70impl AuthSession for StaticTokenAuth {
71 async fn access_token(&self) -> AuthResult<Cow<'_, str>> {
72 Ok(Cow::Borrowed(&self.access_token))
73 }
74
75 fn instance_url(&self) -> &str {
76 &self.instance_url
77 }
78}
79
80#[cfg(test)]
81#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
82mod tests {
83 use super::*;
84
85 #[tokio::test]
86 async fn returns_provided_token_and_url() {
87 let auth = StaticTokenAuth::new("tok", "https://example.my.salesforce.com");
88 assert_eq!(auth.access_token().await.unwrap(), "tok");
89 assert_eq!(auth.instance_url(), "https://example.my.salesforce.com");
90 }
91
92 #[tokio::test]
93 async fn strips_trailing_slash_from_instance_url() {
94 let auth = StaticTokenAuth::new("tok", "https://example.my.salesforce.com/");
95 assert_eq!(auth.instance_url(), "https://example.my.salesforce.com");
96 }
97}