Skip to main content

stack_auth/
access_key_strategy.rs

1use cts_common::{CtsServiceDiscovery, Region, ServiceDiscovery};
2
3use crate::access_key::AccessKey;
4use crate::access_key_refresher::AccessKeyRefresher;
5use crate::auto_refresh::AutoRefresh;
6use crate::{ensure_trailing_slash, AuthError, AuthStrategy, SecretToken, ServiceToken};
7
8/// An [`AuthStrategy`] that uses a static access key to authenticate.
9///
10/// The first call to [`get_token`](AuthStrategy::get_token) authenticates with
11/// the server. Subsequent calls return the cached token until it expires, at
12/// which point re-authentication happens automatically.
13///
14/// # Example
15///
16/// ```no_run
17/// use stack_auth::{AccessKey, AccessKeyStrategy};
18/// use cts_common::Region;
19///
20/// let region = Region::aws("ap-southeast-2").unwrap();
21/// let key: AccessKey = "CSAKmyKeyId.myKeySecret".parse().unwrap();
22/// let strategy = AccessKeyStrategy::new(region, key).unwrap();
23/// ```
24pub struct AccessKeyStrategy {
25    inner: AutoRefresh<AccessKeyRefresher>,
26}
27
28impl AccessKeyStrategy {
29    /// Create a new `AccessKeyStrategy` for the given region and access key.
30    ///
31    /// The auth endpoint is resolved automatically via service discovery.
32    pub fn new(region: Region, access_key: AccessKey) -> Result<Self, AuthError> {
33        Self::builder(region, access_key).build()
34    }
35
36    /// Return a builder for configuring an `AccessKeyStrategy` before construction.
37    ///
38    /// # Example
39    ///
40    /// ```no_run
41    /// use stack_auth::{AccessKey, AccessKeyStrategy};
42    /// use cts_common::Region;
43    ///
44    /// let region = Region::aws("ap-southeast-2").unwrap();
45    /// let key: AccessKey = "CSAKmyKeyId.myKeySecret".parse().unwrap();
46    /// let strategy = AccessKeyStrategy::builder(region, key)
47    ///     .audience("my-audience")
48    ///     .build()
49    ///     .unwrap();
50    /// ```
51    pub fn builder(region: Region, access_key: AccessKey) -> AccessKeyStrategyBuilder {
52        AccessKeyStrategyBuilder {
53            region,
54            access_key: access_key.into_secret_token(),
55            audience: None,
56            base_url_override: None,
57        }
58    }
59}
60
61impl AuthStrategy for &AccessKeyStrategy {
62    async fn get_token(self) -> Result<ServiceToken, AuthError> {
63        Ok(self.inner.get_token().await?)
64    }
65}
66
67/// Builder for [`AccessKeyStrategy`].
68///
69/// Created via [`AccessKeyStrategy::builder`].
70pub struct AccessKeyStrategyBuilder {
71    region: Region,
72    access_key: SecretToken,
73    audience: Option<String>,
74    base_url_override: Option<url::Url>,
75}
76
77impl AccessKeyStrategyBuilder {
78    /// Set the audience for token requests.
79    pub fn audience(mut self, audience: impl Into<String>) -> Self {
80        self.audience = Some(audience.into());
81        self
82    }
83
84    /// Override the base URL resolved by service discovery.
85    ///
86    /// Useful for pointing at a local or mock auth server during testing.
87    #[cfg(any(test, feature = "test-utils"))]
88    pub fn base_url(mut self, url: url::Url) -> Self {
89        self.base_url_override = Some(url);
90        self
91    }
92
93    /// Build the [`AccessKeyStrategy`].
94    ///
95    /// Resolves the base URL via service discovery unless overridden with
96    /// `base_url` (available when the `test-utils` feature is enabled).
97    pub fn build(self) -> Result<AccessKeyStrategy, AuthError> {
98        let base_url = match self.base_url_override {
99            Some(url) => url,
100            None => crate::cts_base_url_from_env()?
101                .unwrap_or(CtsServiceDiscovery::endpoint(self.region)?),
102        };
103        let refresher = AccessKeyRefresher::new(
104            self.access_key,
105            ensure_trailing_slash(base_url),
106            self.audience,
107        );
108        Ok(AccessKeyStrategy {
109            inner: AutoRefresh::new(refresher),
110        })
111    }
112}