server/auth/sas_token_generator.rs
1use crate::service_bus_manager::ServiceBusError;
2use base64::{Engine as _, engine::general_purpose};
3use chrono::{Duration, Utc};
4use hmac::{Hmac, Mac};
5use sha2::Sha256;
6
7type HmacSha256 = Hmac<Sha256>;
8
9/// Generator for Azure Service Bus Shared Access Signature (SAS) tokens.
10///
11/// Creates time-limited authentication tokens using HMAC-SHA256 signing with
12/// shared access keys. SAS tokens provide a secure way to grant limited access
13/// to Service Bus resources without sharing the primary keys.
14///
15/// # Security Notes
16///
17/// - Generated tokens have configurable expiration times
18/// - Uses HMAC-SHA256 for cryptographic signing
19/// - Keys are base64 decoded before use in signing
20/// - Tokens include URL-encoded resource URIs for security
21///
22/// # Examples
23///
24/// ```no_run
25/// use quetty_server::auth::SasTokenGenerator;
26///
27/// let generator = SasTokenGenerator::new("my-namespace".to_string());
28/// let token = generator.generate_sas_token(
29/// "RootManageSharedAccessKey",
30/// "base64_encoded_key",
31/// 24 // 24 hours
32/// )?;
33/// ```
34#[derive(Clone)]
35pub struct SasTokenGenerator {
36 namespace: String,
37}
38
39impl SasTokenGenerator {
40 /// Creates a new SAS token generator for the specified Service Bus namespace.
41 ///
42 /// # Arguments
43 ///
44 /// * `namespace` - The Service Bus namespace (without .servicebus.windows.net)
45 ///
46 /// # Examples
47 ///
48 /// ```no_run
49 /// use quetty_server::auth::SasTokenGenerator;
50 ///
51 /// let generator = SasTokenGenerator::new("my-servicebus-namespace".to_string());
52 /// ```
53 pub fn new(namespace: String) -> Self {
54 Self { namespace }
55 }
56
57 /// Generates a SAS token for Service Bus authentication.
58 ///
59 /// Creates a time-limited Shared Access Signature token using HMAC-SHA256
60 /// signing with the provided shared access key. The token grants access to
61 /// the entire Service Bus namespace.
62 ///
63 /// # Arguments
64 ///
65 /// * `key_name` - The name of the shared access key policy
66 /// * `key` - The base64-encoded shared access key
67 /// * `duration_hours` - Token validity period in hours
68 ///
69 /// # Returns
70 ///
71 /// A complete SAS token string ready for use in Service Bus operations
72 ///
73 /// # Errors
74 ///
75 /// Returns [`ServiceBusError::AuthenticationError`] if:
76 /// - The key cannot be base64 decoded
77 /// - HMAC generation fails
78 /// - Token signing fails
79 ///
80 /// # Examples
81 ///
82 /// ```no_run
83 /// use quetty_server::auth::SasTokenGenerator;
84 ///
85 /// let generator = SasTokenGenerator::new("namespace".to_string());
86 /// let token = generator.generate_sas_token(
87 /// "RootManageSharedAccessKey",
88 /// "base64_encoded_key_here",
89 /// 24 // Valid for 24 hours
90 /// )?;
91 /// ```
92 pub fn generate_sas_token(
93 &self,
94 key_name: &str,
95 key: &str,
96 duration_hours: i64,
97 ) -> Result<String, ServiceBusError> {
98 let expiry = Utc::now() + Duration::hours(duration_hours);
99 let expiry_timestamp = expiry.timestamp();
100
101 let resource_uri = format!("sb://{}.servicebus.windows.net/", self.namespace);
102 let string_to_sign = format!(
103 "{}\n{}",
104 urlencoding::encode(&resource_uri),
105 expiry_timestamp
106 );
107
108 let key_bytes = general_purpose::STANDARD.decode(key).map_err(|e| {
109 ServiceBusError::AuthenticationError(format!("Failed to decode key: {e}"))
110 })?;
111
112 let mut mac = HmacSha256::new_from_slice(&key_bytes).map_err(|e| {
113 ServiceBusError::AuthenticationError(format!("Failed to create HMAC: {e}"))
114 })?;
115
116 mac.update(string_to_sign.as_bytes());
117 let signature = mac.finalize();
118 let signature_base64 = general_purpose::STANDARD.encode(signature.into_bytes());
119
120 let sas_token = format!(
121 "SharedAccessSignature sr={}&sig={}&se={}&skn={}",
122 urlencoding::encode(&resource_uri),
123 urlencoding::encode(&signature_base64),
124 expiry_timestamp,
125 key_name
126 );
127
128 Ok(sas_token)
129 }
130
131 /// Creates a Service Bus connection string from a SAS token.
132 ///
133 /// Combines the namespace endpoint with the SAS token to create a complete
134 /// connection string that can be used for Service Bus operations.
135 ///
136 /// # Arguments
137 ///
138 /// * `sas_token` - A valid SAS token (typically from [`generate_sas_token`])
139 ///
140 /// # Returns
141 ///
142 /// A complete Service Bus connection string
143 ///
144 /// # Examples
145 ///
146 /// ```no_run
147 /// use quetty_server::auth::SasTokenGenerator;
148 ///
149 /// let generator = SasTokenGenerator::new("namespace".to_string());
150 /// let token = generator.generate_sas_token("key_name", "key", 24)?;
151 /// let connection_string = generator.create_connection_string_from_sas(&token);
152 /// ```
153 pub fn create_connection_string_from_sas(&self, sas_token: &str) -> String {
154 format!(
155 "Endpoint=sb://{}.servicebus.windows.net/;SharedAccessSignature={}",
156 self.namespace, sas_token
157 )
158 }
159}