micromegas_auth/
multi.rs

1//! Multi-provider authentication that tries multiple auth methods in sequence.
2
3use crate::types::{AuthContext, AuthProvider};
4use async_trait::async_trait;
5use micromegas_tracing::prelude::*;
6use std::sync::Arc;
7
8/// Multi-provider authentication that tries providers in order until one succeeds.
9///
10/// This provider allows supporting multiple authentication methods simultaneously
11/// and enables adding custom enterprise authentication providers. Providers are
12/// tried in the order they were added via `with_provider()`.
13///
14/// Provider order matters for authentication precedence - the first successful
15/// match wins. Typically, you want faster providers (like API key) before slower
16/// ones (like OIDC JWT validation).
17///
18/// # Example
19///
20/// ```rust,no_run
21/// use micromegas_auth::api_key::{ApiKeyAuthProvider, parse_key_ring};
22/// use micromegas_auth::oidc::{OidcAuthProvider, OidcConfig, OidcIssuer};
23/// use micromegas_auth::multi::MultiAuthProvider;
24/// use std::sync::Arc;
25///
26/// # async fn example() -> anyhow::Result<()> {
27/// // Set up API key provider
28/// let keyring = parse_key_ring(r#"[{"name": "test", "key": "secret"}]"#)?;
29/// let api_key_provider = Arc::new(ApiKeyAuthProvider::new(keyring));
30///
31/// // Set up OIDC provider
32/// let oidc_config = OidcConfig {
33///     issuers: vec![OidcIssuer {
34///         issuer: "https://accounts.google.com".to_string(),
35///         audience: "your-app.apps.googleusercontent.com".to_string(),
36///     }],
37///     jwks_refresh_interval_secs: 3600,
38///     token_cache_size: 1000,
39///     token_cache_ttl_secs: 300,
40/// };
41/// let oidc_provider = Arc::new(OidcAuthProvider::new(oidc_config).await?);
42///
43/// // Create multi-provider with builder pattern
44/// let multi = MultiAuthProvider::new()
45///     .with_provider(api_key_provider)
46///     .with_provider(oidc_provider);
47/// // .with_provider(Arc::new(MyEnterpriseAuthProvider::new())); // Custom provider!
48/// # Ok(())
49/// # }
50/// ```
51pub struct MultiAuthProvider {
52    providers: Vec<Arc<dyn AuthProvider>>,
53}
54
55impl MultiAuthProvider {
56    /// Creates a new empty MultiAuthProvider.
57    #[allow(clippy::new_without_default)]
58    pub fn new() -> Self {
59        Self {
60            providers: Vec::new(),
61        }
62    }
63
64    /// Adds a provider to the authentication chain.
65    ///
66    /// Providers are tried in the order they are added. Returns self for chaining.
67    pub fn with_provider(mut self, provider: Arc<dyn AuthProvider>) -> Self {
68        self.providers.push(provider);
69        self
70    }
71
72    /// Returns true if no providers are configured.
73    pub fn is_empty(&self) -> bool {
74        self.providers.is_empty()
75    }
76}
77
78#[async_trait]
79impl AuthProvider for MultiAuthProvider {
80    async fn validate_request(
81        &self,
82        parts: &dyn crate::types::RequestParts,
83    ) -> anyhow::Result<AuthContext> {
84        for provider in &self.providers {
85            match provider.validate_request(parts).await {
86                Ok(auth_ctx) => {
87                    return Ok(auth_ctx);
88                }
89                Err(e) => {
90                    debug!("partial auth failed: {e:?}");
91                }
92            }
93        }
94        anyhow::bail!("authentication failed with all providers")
95    }
96}