Skip to main content

stack_auth/
access_key_strategy.rs

1use cts_common::{Crn, 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    crn: Option<Crn>,
26    inner: AutoRefresh<AccessKeyRefresher>,
27}
28
29impl AccessKeyStrategy {
30    /// Create a new `AccessKeyStrategy` for the given region and access key.
31    ///
32    /// The auth endpoint is resolved automatically via service discovery.
33    pub fn new(region: Region, access_key: AccessKey) -> Result<Self, AuthError> {
34        Self::builder(region, access_key).build()
35    }
36
37    /// Create a new `AccessKeyStrategy` for the given CRN and access key.
38    ///
39    /// The region is extracted from the CRN for service discovery.
40    /// The full CRN is stored and available via [`workspace_crn`](Self::workspace_crn).
41    pub fn new_with_crn(crn: Crn, access_key: AccessKey) -> Result<Self, AuthError> {
42        Self::builder(crn.region, access_key).crn(crn).build()
43    }
44
45    /// Return a builder for configuring an `AccessKeyStrategy` before construction.
46    ///
47    /// # Example
48    ///
49    /// ```no_run
50    /// use stack_auth::{AccessKey, AccessKeyStrategy};
51    /// use cts_common::Region;
52    ///
53    /// let region = Region::aws("ap-southeast-2").unwrap();
54    /// let key: AccessKey = "CSAKmyKeyId.myKeySecret".parse().unwrap();
55    /// let strategy = AccessKeyStrategy::builder(region, key)
56    ///     .audience("my-audience")
57    ///     .build()
58    ///     .unwrap();
59    /// ```
60    pub fn builder(region: Region, access_key: AccessKey) -> AccessKeyStrategyBuilder {
61        AccessKeyStrategyBuilder {
62            region,
63            access_key: access_key.into_secret_token(),
64            audience: None,
65            base_url_override: None,
66            crn: None,
67        }
68    }
69
70    /// Return the workspace CRN, if one was provided at construction time.
71    pub fn workspace_crn(&self) -> Option<&Crn> {
72        self.crn.as_ref()
73    }
74}
75
76impl AuthStrategy for &AccessKeyStrategy {
77    async fn get_token(self) -> Result<ServiceToken, AuthError> {
78        Ok(self.inner.get_token().await?)
79    }
80}
81
82/// Builder for [`AccessKeyStrategy`].
83///
84/// Created via [`AccessKeyStrategy::builder`].
85pub struct AccessKeyStrategyBuilder {
86    region: Region,
87    access_key: SecretToken,
88    audience: Option<String>,
89    base_url_override: Option<url::Url>,
90    crn: Option<Crn>,
91}
92
93impl AccessKeyStrategyBuilder {
94    /// Set the audience for token requests.
95    pub fn audience(mut self, audience: impl Into<String>) -> Self {
96        self.audience = Some(audience.into());
97        self
98    }
99
100    /// Associate a workspace CRN with this strategy.
101    pub fn crn(mut self, crn: Crn) -> Self {
102        self.crn = Some(crn);
103        self
104    }
105
106    /// Override the base URL resolved by service discovery.
107    ///
108    /// Useful for pointing at a local or mock auth server during testing.
109    #[cfg(any(test, feature = "test-utils"))]
110    pub fn base_url(mut self, url: url::Url) -> Self {
111        self.base_url_override = Some(url);
112        self
113    }
114
115    /// Build the [`AccessKeyStrategy`].
116    ///
117    /// Resolves the base URL via service discovery unless overridden with
118    /// `base_url` (available when the `test-utils` feature is enabled).
119    pub fn build(self) -> Result<AccessKeyStrategy, AuthError> {
120        let base_url = match self.base_url_override {
121            Some(url) => url,
122            None => crate::cts_base_url_from_env()?
123                .unwrap_or(CtsServiceDiscovery::endpoint(self.region)?),
124        };
125        let refresher = AccessKeyRefresher::new(
126            self.access_key,
127            ensure_trailing_slash(base_url),
128            self.audience,
129        );
130        Ok(AccessKeyStrategy {
131            crn: self.crn,
132            inner: AutoRefresh::new(refresher),
133        })
134    }
135}