server/auth/
connection_string.rs

1use super::provider::{AuthProvider, AuthToken};
2use super::sas_token_generator::SasTokenGenerator;
3use super::types::{AuthType, ConnectionStringConfig};
4use crate::service_bus_manager::ServiceBusError;
5use async_trait::async_trait;
6
7#[derive(Clone)]
8pub struct ConnectionStringProvider {
9    config: ConnectionStringConfig,
10    key_name: String,
11    key: String,
12    sas_generator: SasTokenGenerator,
13}
14
15impl ConnectionStringProvider {
16    /// Creates a new ConnectionStringProvider from a connection string configuration.
17    ///
18    /// Parses the connection string to extract the namespace, shared access key name,
19    /// and shared access key. Validates that all required components are present.
20    ///
21    /// # Arguments
22    ///
23    /// * `config` - Configuration containing the Service Bus connection string
24    ///
25    /// # Returns
26    ///
27    /// A configured ConnectionStringProvider ready for authentication
28    ///
29    /// # Errors
30    ///
31    /// Returns [`ServiceBusError::ConfigurationError`] if:
32    /// - Connection string is empty
33    /// - Connection string is missing required components (Endpoint, SharedAccessKeyName, SharedAccessKey)
34    /// - Connection string format is invalid
35    ///
36    /// # Examples
37    ///
38    /// ```no_run
39    /// use quetty_server::auth::{ConnectionStringProvider, ConnectionStringConfig};
40    ///
41    /// let config = ConnectionStringConfig {
42    ///     value: "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=abc123".to_string(),
43    /// };
44    ///
45    /// let provider = ConnectionStringProvider::new(config)?;
46    /// ```
47    pub fn new(config: ConnectionStringConfig) -> Result<Self, ServiceBusError> {
48        if config.value.is_empty() {
49            return Err(ServiceBusError::ConfigurationError(
50                "Connection string cannot be empty".to_string(),
51            ));
52        }
53
54        // Parse connection string to extract components
55        let mut namespace = None;
56        let mut key_name = None;
57        let mut key = None;
58
59        for part in config.value.split(';') {
60            let part = part.trim();
61            if part.is_empty() {
62                continue;
63            }
64
65            if let Some(endpoint) = part.strip_prefix("Endpoint=") {
66                // Extract namespace from endpoint like "sb://namespace.servicebus.windows.net/"
67                if let Some(ns_start) = endpoint.find("://") {
68                    let ns_part = &endpoint[ns_start + 3..];
69                    if let Some(dot_pos) = ns_part.find('.') {
70                        namespace = Some(ns_part[..dot_pos].to_string());
71                    }
72                }
73            } else if let Some(kn) = part.strip_prefix("SharedAccessKeyName=") {
74                key_name = Some(kn.to_string());
75            } else if let Some(k) = part.strip_prefix("SharedAccessKey=") {
76                key = Some(k.to_string());
77            }
78        }
79
80        let namespace = namespace.ok_or_else(|| {
81            ServiceBusError::ConfigurationError(
82                "Missing namespace in connection string".to_string(),
83            )
84        })?;
85        let key_name = key_name.ok_or_else(|| {
86            ServiceBusError::ConfigurationError(
87                "Missing SharedAccessKeyName in connection string".to_string(),
88            )
89        })?;
90        let key = key.ok_or_else(|| {
91            ServiceBusError::ConfigurationError(
92                "Missing SharedAccessKey in connection string".to_string(),
93            )
94        })?;
95
96        let sas_generator = SasTokenGenerator::new(namespace.clone());
97
98        Ok(Self {
99            config,
100            key_name,
101            key,
102            sas_generator,
103        })
104    }
105
106    /// Gets the original connection string value.
107    ///
108    /// # Returns
109    ///
110    /// The complete connection string as provided in the configuration
111    pub fn connection_string(&self) -> &str {
112        &self.config.value
113    }
114}
115
116#[async_trait]
117impl AuthProvider for ConnectionStringProvider {
118    /// Authenticates using the connection string by generating a SAS token.
119    ///
120    /// Creates a time-limited SAS token (24 hours) using the shared access key
121    /// from the connection string. The token can be used to authenticate
122    /// Service Bus operations.
123    ///
124    /// # Returns
125    ///
126    /// An [`AuthToken`] containing the SAS-based connection string and expiration
127    ///
128    /// # Errors
129    ///
130    /// Returns [`ServiceBusError`] if SAS token generation fails
131    async fn authenticate(&self) -> Result<AuthToken, ServiceBusError> {
132        // Generate a SAS token valid for 24 hours
133        let sas_token = self.sas_generator.generate_sas_token(
134            &self.key_name,
135            &self.key,
136            24, // 24 hours validity
137        )?;
138
139        // Create a connection string with the SAS token
140        let connection_string = self
141            .sas_generator
142            .create_connection_string_from_sas(&sas_token);
143
144        Ok(AuthToken {
145            token: connection_string,
146            token_type: "ConnectionString".to_string(),
147            expires_in_secs: Some(24 * 3600), // 24 hours in seconds
148        })
149    }
150
151    /// Returns the authentication type for this provider.
152    ///
153    /// # Returns
154    ///
155    /// [`AuthType::ConnectionString`] indicating connection string authentication
156    fn auth_type(&self) -> AuthType {
157        AuthType::ConnectionString
158    }
159
160    /// Indicates whether this provider's tokens require periodic refresh.
161    ///
162    /// Connection string authentication uses SAS tokens that expire,
163    /// so refresh is required.
164    ///
165    /// # Returns
166    ///
167    /// `true` because SAS tokens have limited validity
168    fn requires_refresh(&self) -> bool {
169        true // SAS tokens expire, so we need refresh
170    }
171}