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}