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}