libauth_rs/providers/
mod.rs

1#[cfg(feature = "clerk")]
2pub mod clerk;
3#[cfg(feature = "msal")]
4pub mod msal;
5#[cfg(feature = "stytch")]
6pub mod stytch;
7
8#[cfg(feature = "clerk")]
9pub use clerk::ClerkProvider;
10#[cfg(feature = "msal")]
11pub use msal::MsalProvider;
12#[cfg(feature = "stytch")]
13pub use stytch::StytchProvider;
14
15use crate::error::{AuthError, AuthResult};
16use crate::types::{AuthProvider as AuthProviderType, AuthToken, UserContext};
17use async_trait::async_trait;
18
19/// Trait that all authentication providers must implement
20#[async_trait]
21pub trait AuthProvider: Send + Sync {
22    /// Get the name of this provider (for logging/debugging)
23    fn name(&self) -> &'static str;
24
25    /// Get the provider type
26    fn provider_type(&self) -> AuthProviderType;
27
28    /// Check if this provider can handle the given token
29    /// This is typically a quick check based on token format, issuer, etc.
30    async fn can_handle(&self, token: &AuthToken) -> bool;
31
32    /// Authenticate the token and return user context
33    /// This method should validate the token and extract user information
34    async fn authenticate(&self, token: &AuthToken) -> AuthResult<UserContext>;
35
36    /// Optional: Get the expected issuer(s) for this provider
37    /// This is used for automatic issuer-based routing
38    fn expected_issuers(&self) -> Vec<String> {
39        Vec::new()
40    }
41
42    /// Optional: Refresh a token if the provider supports it
43    async fn refresh_token(&self, _refresh_token: &str) -> AuthResult<String> {
44        Err(AuthError::Unknown {
45            message: "Token refresh not supported by this provider".to_string(),
46            provider: Some(self.name().to_string()),
47        })
48    }
49
50    /// Optional: Revoke/logout a token
51    async fn revoke_token(&self, _token: &AuthToken) -> AuthResult<()> {
52        // Default implementation does nothing
53        // Providers can override if they support token revocation
54        Ok(())
55    }
56
57    /// Check if a user has a specific permission
58    /// Providers can implement their own permission checking logic
59    async fn check_permission(&self, user: &UserContext, permission: &str) -> AuthResult<bool> {
60        // Default implementation checks metadata for permission flags
61        Ok(user
62            .metadata
63            .get(&format!("permission_{}", permission))
64            .and_then(|v| v.as_bool())
65            .unwrap_or(false))
66    }
67
68    /// Get all roles for a user
69    /// Providers should override this to fetch roles from their API
70    async fn get_user_roles(&self, user: &UserContext) -> AuthResult<Vec<String>> {
71        // Default implementation extracts roles from metadata
72        Ok(user.get_roles())
73    }
74
75    /// Check if a user has a specific role
76    async fn check_role(&self, user: &UserContext, role: &str) -> AuthResult<bool> {
77        let roles = self.get_user_roles(user).await?;
78        Ok(roles.iter().any(|r| r == role))
79    }
80
81    /// Check if a user belongs to a specific organization/tenant
82    async fn check_organization(&self, user: &UserContext, org_id: &str) -> AuthResult<bool> {
83        // Default implementation checks metadata
84        Ok(user
85            .metadata
86            .get("organization_id")
87            .or_else(|| user.metadata.get("org_id"))
88            .or_else(|| user.metadata.get("tenant_id"))
89            .and_then(|v| v.as_str())
90            .map(|id| id == org_id)
91            .unwrap_or(false))
92    }
93}
94
95/// Configuration for authentication providers
96#[derive(Debug, Clone, Default)]
97pub struct AuthConfig {
98    #[cfg(feature = "clerk")]
99    pub clerk_publishable_key: Option<String>,
100    #[cfg(feature = "clerk")]
101    pub clerk_secret_key: Option<String>,
102
103    #[cfg(feature = "stytch")]
104    pub stytch_project_id: Option<String>,
105    #[cfg(feature = "stytch")]
106    pub stytch_secret: Option<String>,
107
108    #[cfg(feature = "msal")]
109    pub azure_client_id: Option<String>,
110    #[cfg(feature = "msal")]
111    pub azure_client_secret: Option<String>,
112    #[cfg(feature = "msal")]
113    pub azure_tenant_id: Option<String>,
114
115    /// Optional: Issuer-to-provider mapping for routing based on JWT issuer (iss) claim
116    /// Maps issuer strings (e.g., "<https://clerk.example.com>", "<https://login.microsoftonline.com>") to provider types
117    pub issuer_to_provider: Option<std::collections::HashMap<String, AuthProviderType>>,
118}
119
120impl AuthConfig {
121    /// Add an issuer-to-provider mapping
122    /// Use this to map JWT issuer (iss) claims to specific providers
123    pub fn with_issuer_mapping(
124        mut self,
125        issuer: impl Into<String>,
126        provider: AuthProviderType,
127    ) -> Self {
128        if self.issuer_to_provider.is_none() {
129            self.issuer_to_provider = Some(std::collections::HashMap::new());
130        }
131        if let Some(ref mut map) = self.issuer_to_provider {
132            map.insert(issuer.into(), provider);
133        }
134        self
135    }
136
137    /// Get provider for a given issuer
138    pub fn get_provider_for_issuer(&self, issuer: &str) -> Option<AuthProviderType> {
139        self.issuer_to_provider
140            .as_ref()
141            .and_then(|map| map.get(issuer).copied())
142    }
143}