tower_oauth2_resource_server/
builder.rs

1use std::{marker::PhantomData, sync::Arc};
2
3use serde::de::DeserializeOwned;
4
5use crate::{
6    auth_resolver::{AuthorizerResolver, IssuerAuthorizerResolver, SingleAuthorizerResolver},
7    error::StartupError,
8    server::OAuth2ResourceServer,
9    tenant::TenantConfiguration,
10};
11
12#[derive(Debug)]
13pub struct OAuth2ResourceServerBuilder<Claims>
14where
15    Claims: Clone + DeserializeOwned + Send + Sync + 'static,
16{
17    tenant_configurations: Vec<TenantConfiguration>,
18    auth_resolver: Option<Arc<dyn AuthorizerResolver<Claims>>>,
19    phantom: PhantomData<Claims>,
20}
21
22impl<Claims> OAuth2ResourceServer<Claims>
23where
24    Claims: Clone + DeserializeOwned + Send + Sync + 'static,
25{
26    pub fn builder() -> OAuth2ResourceServerBuilder<Claims> {
27        OAuth2ResourceServerBuilder::new()
28    }
29}
30
31impl<Claims> OAuth2ResourceServerBuilder<Claims>
32where
33    Claims: Clone + DeserializeOwned + Send + Sync + 'static,
34{
35    fn new() -> Self {
36        OAuth2ResourceServerBuilder::<Claims> {
37            tenant_configurations: Vec::new(),
38            auth_resolver: None,
39            phantom: PhantomData,
40        }
41    }
42
43    /// Add a tenant (authorization server).
44    pub fn add_tenant(mut self, tenant_configuration: TenantConfiguration) -> Self {
45        self.tenant_configurations.push(tenant_configuration);
46        self
47    }
48
49    /// Provide a custom authorization resolver.
50    ///
51    /// Only needs to be provided if the default resolver is not sufficient.
52    ///
53    /// See [AuthorizerResolver] for more information.
54    pub fn auth_resolver(mut self, auth_resolver: Arc<dyn AuthorizerResolver<Claims>>) -> Self {
55        self.auth_resolver = Some(auth_resolver);
56        self
57    }
58
59    /// Construct an OAuth2ResourceServer.
60    ///
61    /// During construction the OIDC Provider Configuration endpoint of the
62    /// authorization server might be queried.
63    /// Thus, the operation can fail and therefore returns a Result.
64    pub async fn build(self) -> Result<OAuth2ResourceServer<Claims>, StartupError> {
65        if self.tenant_configurations.is_empty() {
66            return Err(StartupError::InvalidParameter(
67                "At least one TenantConfiguration is required".to_owned(),
68            ));
69        }
70        let num_tenants = self.tenant_configurations.len();
71        let auth_resolver = self.auth_resolver.unwrap_or_else(|| {
72            if num_tenants == 1 {
73                Arc::new(SingleAuthorizerResolver {})
74            } else {
75                Arc::new(IssuerAuthorizerResolver {})
76            }
77        });
78        OAuth2ResourceServer::new(self.tenant_configurations, auth_resolver).await
79    }
80}
81
82impl<Claims> Default for OAuth2ResourceServerBuilder<Claims>
83where
84    Claims: Clone + DeserializeOwned + Send + Sync + 'static,
85{
86    fn default() -> Self {
87        Self::new()
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use serde::Deserialize;
94
95    use super::*;
96
97    #[derive(Clone, Debug, Deserialize)]
98    struct Claims {}
99
100    #[tokio::test]
101    async fn should_require_tenant_configurations() {
102        let result = OAuth2ResourceServerBuilder::<Claims>::new().build().await;
103        assert!(result.is_err());
104        assert_eq!(
105            result.unwrap_err(),
106            StartupError::InvalidParameter(
107                "At least one TenantConfiguration is required".to_owned()
108            )
109        )
110    }
111}