Skip to main content

oauth_provider_rs/providers/
provider_trait.rs

1//! OAuth Provider Trait Abstraction
2//!
3//! This module provides trait abstractions for OAuth providers to support
4//! multiple OAuth providers (GitHub, Cognito, etc.) following microkernel principles.
5
6use crate::{client_manager::ClientManager, storage::OAuthStorage};
7use async_trait::async_trait;
8use axum::Router;
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12// Use the unified config from http_integration
13pub type OAuthProviderConfig = crate::http_integration::config::OAuthProviderConfig;
14
15/// Token response from OAuth provider
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct TokenResponse {
18    pub access_token: String,
19    pub token_type: String,
20    pub scope: String,
21    pub expires_in: Option<u64>,
22    pub refresh_token: Option<String>,
23}
24
25/// User information from OAuth provider
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct UserInfo {
28    pub user_id: String,
29    pub username: String,
30    pub name: Option<String>,
31    pub email: Option<String>,
32    pub avatar_url: Option<String>,
33    pub provider_specific: Value,
34}
35
36/// User properties for authorization completion
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct UserProps {
39    pub user_id: String,
40    pub username: String,
41    pub name: Option<String>,
42    pub email: Option<String>,
43    pub access_token: String,
44    pub provider_specific: Value,
45}
46
47/// OAuth provider trait for abstracting different OAuth providers
48#[async_trait]
49pub trait OAuthProviderTrait<S: OAuthStorage + Clone, CM: ClientManager + Clone>:
50    Send + Sync + Clone
51{
52    /// Get the provider name (e.g., "github", "cognito", etc.)
53    fn provider_name(&self) -> &str;
54
55    /// Get the configuration for this provider
56    fn config(&self) -> &OAuthProviderConfig;
57
58    /// Create router for OAuth endpoints
59    fn router(self) -> Router;
60
61    /// Get user information from the provider
62    async fn get_user_info(&self, access_token: &str) -> Result<UserInfo, String>;
63
64    /// Convert user info to user properties for authorization completion
65    fn user_info_to_props(&self, user_info: &UserInfo, access_token: &str) -> UserProps {
66        UserProps {
67            user_id: user_info.user_id.clone(),
68            username: user_info.username.clone(),
69            name: user_info.name.clone(),
70            email: user_info.email.clone(),
71            access_token: access_token.to_string(),
72            provider_specific: user_info.provider_specific.clone(),
73        }
74    }
75
76    /// Get storage reference
77    fn storage(&self) -> &S;
78
79    /// Get client manager reference
80    fn client_manager(&self) -> &CM;
81}
82
83/// Provider type enum for identifying different OAuth providers
84#[derive(Debug, Clone, PartialEq, Eq, Hash)]
85pub enum ProviderType {
86    GitHub,
87    Cognito,
88    Google,
89    Microsoft,
90    Custom(String),
91}
92
93impl std::fmt::Display for ProviderType {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        match self {
96            ProviderType::GitHub => write!(f, "github"),
97            ProviderType::Cognito => write!(f, "cognito"),
98            ProviderType::Google => write!(f, "google"),
99            ProviderType::Microsoft => write!(f, "microsoft"),
100            ProviderType::Custom(name) => write!(f, "{}", name),
101        }
102    }
103}
104
105impl From<&str> for ProviderType {
106    fn from(s: &str) -> Self {
107        match s.to_lowercase().as_str() {
108            "github" => ProviderType::GitHub,
109            "cognito" => ProviderType::Cognito,
110            "google" => ProviderType::Google,
111            "microsoft" => ProviderType::Microsoft,
112            _ => ProviderType::Custom(s.to_string()),
113        }
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_provider_type_display() {
123        assert_eq!(ProviderType::GitHub.to_string(), "github");
124        assert_eq!(ProviderType::Cognito.to_string(), "cognito");
125        assert_eq!(
126            ProviderType::Custom("custom".to_string()).to_string(),
127            "custom"
128        );
129    }
130
131    #[test]
132    fn test_provider_type_from_str() {
133        assert_eq!(ProviderType::from("github"), ProviderType::GitHub);
134        assert_eq!(ProviderType::from("GitHub"), ProviderType::GitHub);
135        assert_eq!(ProviderType::from("cognito"), ProviderType::Cognito);
136        assert_eq!(
137            ProviderType::from("custom"),
138            ProviderType::Custom("custom".to_string())
139        );
140    }
141
142    #[test]
143    fn test_oauth_provider_config() {
144        let config = OAuthProviderConfig::with_oauth_config(
145            "test_client".to_string(),
146            "test_secret".to_string(),
147            "http://localhost:8080/callback".to_string(),
148            "read:user".to_string(),
149            "github".to_string(),
150        );
151
152        assert_eq!(config.provider.client_id.as_ref().unwrap(), "test_client");
153        assert_eq!(config.provider.provider_name.as_ref().unwrap(), "github");
154    }
155
156    #[test]
157    fn test_unified_config_conversion() {
158        let config = OAuthProviderConfig::with_oauth_config(
159            "test_client".to_string(),
160            "test_secret".to_string(),
161            "http://localhost:8080/callback".to_string(),
162            "read:user".to_string(),
163            "github".to_string(),
164        );
165
166        assert_eq!(config.provider.client_id.as_ref().unwrap(), "test_client");
167        assert_eq!(config.provider.client_secret.as_ref().unwrap(), "test_secret");
168        assert_eq!(config.provider.redirect_uri.as_ref().unwrap(), "http://localhost:8080/callback");
169        assert_eq!(config.provider.scope.as_ref().unwrap(), "read:user");
170        assert_eq!(config.provider.provider_name.as_ref().unwrap(), "github");
171    }
172}