server/auth/auth_provider.rs
1use super::auth_state::{AuthStateManager, AuthenticationState};
2use super::provider::{AuthProvider as AuthProviderTrait, AuthToken};
3use super::types::AuthType;
4use crate::service_bus_manager::ServiceBusError;
5use async_trait::async_trait;
6use std::sync::Arc;
7
8/// Authentication provider that integrates with UI authentication state.
9///
10/// Provides a bridge between the server-side authentication system and the UI
11/// authentication state, enabling seamless authentication flow coordination between
12/// the terminal interface and Azure Service Bus operations.
13///
14/// This provider prioritizes UI-managed authentication tokens and provides fallback
15/// mechanisms for automated scenarios where UI authentication is not available.
16///
17/// See also: [`AuthStateManager`] for state management, [`AuthProvider`](crate::auth::provider::AuthProvider) for the base trait.
18///
19/// # Architecture
20///
21/// The [`AuthProvider`] implements a hierarchical authentication strategy:
22///
23/// 1. **UI State Priority** - First checks for valid tokens from UI authentication
24/// 2. **State-based Authentication** - Uses centralized authentication state management
25/// 3. **Fallback Provider** - Falls back to alternative authentication methods if available
26/// 4. **Error Propagation** - Provides detailed error feedback for authentication failures
27///
28/// # Authentication Flow
29///
30/// ```no_run
31/// use quetty_server::auth::{AuthProvider, AuthStateManager};
32/// use std::sync::Arc;
33///
34/// // Create with UI authentication state integration
35/// let auth_state = Arc::new(AuthStateManager::new());
36/// let provider = AuthProvider::new(auth_state, None);
37///
38/// // Authenticate using UI state or fallback methods
39/// match provider.authenticate().await {
40/// Ok(token) => {
41/// println!("Authentication successful: {}", token.token);
42/// // Use token for Service Bus operations
43/// }
44/// Err(e) => eprintln!("Authentication failed: {}", e),
45/// }
46/// ```
47///
48/// # Integration with UI Authentication
49///
50/// This provider seamlessly integrates with UI authentication flows:
51///
52/// - **Device Code Flow** - Coordinates with UI device code authentication
53/// - **Token Management** - Uses UI-managed token cache and refresh logic
54/// - **State Synchronization** - Maintains consistency between UI and server authentication
55/// - **Error Handling** - Provides user-friendly error messages for UI display
56///
57/// # Fallback Authentication
58///
59/// When UI authentication is not available, the provider can use fallback methods:
60///
61/// For more details on fallback providers, see [`ConnectionStringProvider`].
62///
63/// ```no_run
64/// use quetty_server::auth::{AuthProvider, AuthStateManager, ConnectionStringProvider};
65/// use std::sync::Arc;
66///
67/// // Create fallback provider for automated scenarios
68/// let connection_provider = Arc::new(ConnectionStringProvider::new(config)?);
69/// let auth_state = Arc::new(AuthStateManager::new());
70///
71/// let provider = AuthProvider::new(
72/// auth_state,
73/// Some(connection_provider as Arc<dyn AuthProviderTrait>)
74/// );
75///
76/// // Will use UI state if available, otherwise fall back to connection string
77/// let token = provider.authenticate().await?;
78/// ```
79///
80/// # Thread Safety
81///
82/// The provider is designed for concurrent access and can be safely shared across
83/// multiple threads and async tasks. All internal state is protected by appropriate
84/// synchronization mechanisms.
85///
86/// # Error Handling
87///
88/// Provides comprehensive error handling for various authentication scenarios:
89///
90/// - **Not Authenticated** - Clear guidance for users to authenticate through UI
91/// - **Authentication in Progress** - Informative messages during device code flow
92/// - **Authentication Failed** - Detailed error information from underlying providers
93///
94/// All errors are returned as [`ServiceBusError`](crate::service_bus_manager::ServiceBusError) variants.
95/// - **Token Refresh Failures** - Graceful handling of token expiration scenarios
96pub struct AuthProvider {
97 /// Centralized authentication state manager shared with UI components
98 auth_state: Arc<AuthStateManager>,
99 /// Optional fallback authentication provider for automated scenarios
100 fallback_provider: Option<Arc<dyn AuthProviderTrait>>,
101}
102
103impl AuthProvider {
104 /// Creates a new authentication provider with UI state integration.
105 ///
106 /// # Arguments
107 ///
108 /// * `auth_state` - Shared authentication state manager for UI coordination
109 /// * `fallback_provider` - Optional fallback provider for automated scenarios
110 ///
111 /// # Returns
112 ///
113 /// A new `AuthProvider` instance ready for authentication operations
114 ///
115 /// # Examples
116 ///
117 /// ```no_run
118 /// use quetty_server::auth::{AuthProvider, AuthStateManager};
119 /// use std::sync::Arc;
120 ///
121 /// // Basic provider with UI state only
122 /// let auth_state = Arc::new(AuthStateManager::new());
123 /// let provider = AuthProvider::new(auth_state, None);
124 ///
125 /// // Provider with fallback for automated scenarios
126 /// let auth_state = Arc::new(AuthStateManager::new());
127 /// let fallback = Arc::new(connection_provider);
128 /// let provider = AuthProvider::new(auth_state, Some(fallback));
129 /// ```
130 pub fn new(
131 auth_state: Arc<AuthStateManager>,
132 fallback_provider: Option<Arc<dyn AuthProviderTrait>>,
133 ) -> Self {
134 Self {
135 auth_state,
136 fallback_provider,
137 }
138 }
139}
140
141#[async_trait]
142impl AuthProviderTrait for AuthProvider {
143 async fn authenticate(&self) -> Result<AuthToken, ServiceBusError> {
144 // First check if we have a valid token from UI authentication
145 if let Some(token) = self.auth_state.get_azure_ad_token().await {
146 return Ok(AuthToken {
147 token,
148 token_type: "Bearer".to_string(),
149 expires_in_secs: Some(3600), // Default 1 hour
150 });
151 }
152
153 // Check the authentication state
154 match self.auth_state.get_state().await {
155 AuthenticationState::Authenticated { token, .. } => {
156 Ok(AuthToken {
157 token,
158 token_type: "Bearer".to_string(),
159 expires_in_secs: Some(3600), // Default 1 hour
160 })
161 }
162 AuthenticationState::AwaitingDeviceCode { .. } => {
163 Err(ServiceBusError::AuthenticationError(
164 "Authentication in progress. Please complete device code authentication in the UI.".to_string()
165 ))
166 }
167 AuthenticationState::Failed(error) => {
168 Err(ServiceBusError::AuthenticationError(
169 format!("Authentication failed: {error}")
170 ))
171 }
172 AuthenticationState::NotAuthenticated => {
173 // If we have a fallback provider, try it
174 if let Some(fallback) = &self.fallback_provider {
175 fallback.authenticate().await
176 } else {
177 Err(ServiceBusError::AuthenticationError(
178 "Not authenticated. Please authenticate through the UI first.".to_string()
179 ))
180 }
181 }
182 }
183 }
184
185 async fn refresh(&self) -> Result<AuthToken, ServiceBusError> {
186 // For now, just try to authenticate again
187 self.authenticate().await
188 }
189
190 fn auth_type(&self) -> AuthType {
191 AuthType::AzureAd
192 }
193
194 fn requires_refresh(&self) -> bool {
195 // Let the auth state manager handle refresh logic
196 false
197 }
198}