Skip to main content

securitydept_token_set_context/access_token_substrate/
runtime.rs

1use std::sync::Arc;
2
3use securitydept_oauth_resource_server::OAuthResourceServerVerifier;
4
5use super::{
6    capabilities::TokenPropagation,
7    propagation::{TokenPropagator, TokenPropagatorError},
8};
9
10/// Error building an [`AccessTokenSubstrateRuntime`].
11#[derive(Debug)]
12pub enum AccessTokenSubstrateRuntimeError {
13    /// Failed to build the resource-server verifier.
14    ResourceServer(securitydept_oauth_resource_server::OAuthResourceServerError),
15    /// Failed to build the token propagator.
16    TokenPropagator(TokenPropagatorError),
17}
18
19impl std::fmt::Display for AccessTokenSubstrateRuntimeError {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        match self {
22            Self::ResourceServer(e) => write!(f, "resource_server: {e}"),
23            Self::TokenPropagator(e) => write!(f, "token_propagation: {e}"),
24        }
25    }
26}
27
28impl std::error::Error for AccessTokenSubstrateRuntimeError {
29    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
30        match self {
31            Self::ResourceServer(e) => Some(e),
32            Self::TokenPropagator(e) => Some(e),
33        }
34    }
35}
36
37/// Unified runtime for the access-token substrate layer.
38///
39/// Owns the token propagator (which may be absent when propagation is
40/// disabled). The resource-server verifier lives alongside in
41/// [`ServerState`](../../apps/server/src/state) so it can be accessed
42/// independently, mirroring the pattern used by `oidc_client`.
43#[derive(Clone)]
44pub struct AccessTokenSubstrateRuntime {
45    token_propagator: Option<Arc<TokenPropagator>>,
46}
47
48impl std::fmt::Debug for AccessTokenSubstrateRuntime {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        f.debug_struct("AccessTokenSubstrateRuntime")
51            .field("token_propagator", &self.token_propagator)
52            .finish()
53    }
54}
55
56impl AccessTokenSubstrateRuntime {
57    /// **Recommended entry point.** Build the substrate runtime from a
58    /// [`ResolvedAccessTokenSubstrateConfig`], returning the runtime together
59    /// with an optional resource-server verifier.
60    ///
61    /// The verifier is returned separately so that the caller can store it
62    /// independently (e.g. as `oauth_resource_server_verifier` on
63    /// `ServerState`), mirroring the pattern used by `oidc_client`.
64    ///
65    /// ```text
66    /// ResolvedAccessTokenSubstrateConfig
67    ///   ──▸ from_resolved_config()
68    ///   ──▸ (AccessTokenSubstrateRuntime, Option<Arc<OAuthResourceServerVerifier>>)
69    /// ```
70    pub async fn from_resolved_config(
71        config: &super::config::ResolvedAccessTokenSubstrateConfig,
72    ) -> Result<(Self, Option<Arc<OAuthResourceServerVerifier>>), AccessTokenSubstrateRuntimeError>
73    {
74        let resource_verifier = if config.resource_server.remote.is_discovery_configured() {
75            let verifier = OAuthResourceServerVerifier::from_config(config.resource_server.clone())
76                .await
77                .map_err(AccessTokenSubstrateRuntimeError::ResourceServer)?;
78            Some(Arc::new(verifier))
79        } else {
80            None
81        };
82
83        let runtime = Self::new(&config.token_propagation)?;
84        Ok((runtime, resource_verifier))
85    }
86
87    /// Build from a `TokenPropagation` axis.
88    ///
89    /// Lower-level constructor — prefer
90    /// [`from_resolved_config`](Self::from_resolved_config) when you have a
91    /// [`ResolvedAccessTokenSubstrateConfig`].
92    pub fn new(
93        token_propagation: &TokenPropagation,
94    ) -> Result<Self, AccessTokenSubstrateRuntimeError> {
95        let token_propagator = match token_propagation {
96            TokenPropagation::Enabled { config } => {
97                let propagator = TokenPropagator::from_config(config)
98                    .map_err(AccessTokenSubstrateRuntimeError::TokenPropagator)?;
99                Some(Arc::new(propagator))
100            }
101            TokenPropagation::Disabled => None,
102        };
103
104        Ok(Self { token_propagator })
105    }
106
107    /// Access the token propagator, if enabled.
108    pub fn token_propagator(&self) -> Option<&Arc<TokenPropagator>> {
109        self.token_propagator.as_ref()
110    }
111
112    /// Whether propagation is enabled (propagator is present).
113    pub fn propagation_enabled(&self) -> bool {
114        self.token_propagator.is_some()
115    }
116
117    /// Build a forwarder from this runtime and a forwarder config source.
118    ///
119    /// Returns `None` if propagation is disabled (no propagator available).
120    /// Returns `Some(Err(...))` if the forwarder config is present but
121    /// construction fails.
122    pub fn build_forwarder<C>(&self, config: &C) -> Option<Result<C::Forwarder, C::Error>>
123    where
124        C: super::forwarder::PropagationForwarderConfigSource,
125    {
126        if !self.propagation_enabled() {
127            return None;
128        }
129        Some(config.build_forwarder())
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136    use crate::access_token_substrate::{
137        capabilities::TokenPropagation,
138        propagation::{PropagationDestinationPolicy, TokenPropagatorConfig},
139    };
140
141    #[test]
142    fn runtime_returns_none_propagator_when_disabled() {
143        let runtime = AccessTokenSubstrateRuntime::new(&TokenPropagation::Disabled)
144            .expect("should not error");
145        assert!(runtime.token_propagator().is_none());
146        assert!(!runtime.propagation_enabled());
147    }
148
149    #[test]
150    fn runtime_returns_some_propagator_when_enabled() {
151        let runtime = AccessTokenSubstrateRuntime::new(&TokenPropagation::Enabled {
152            config: TokenPropagatorConfig {
153                destination_policy: PropagationDestinationPolicy {
154                    allowed_targets: vec![],
155                    ..Default::default()
156                },
157                ..Default::default()
158            },
159        })
160        .expect("should not error");
161        assert!(runtime.token_propagator().is_some());
162        assert!(runtime.propagation_enabled());
163    }
164}