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}