pub mod auth0;
pub mod azure_ad;
pub mod github;
pub mod google;
pub mod keycloak;
pub mod logto;
pub mod okta;
pub mod ory;
pub use auth0::Auth0OAuth;
pub use azure_ad::AzureADOAuth;
pub use github::GitHubOAuth;
pub use google::GoogleOAuth;
pub use keycloak::KeycloakOAuth;
pub use logto::LogtoOAuth;
pub use okta::OktaOAuth;
pub use ory::OryOAuth;
use crate::{error::Result, provider::OAuthProvider};
pub async fn create_provider(
provider_type: &str,
client_id: String,
client_secret: String,
redirect_uri: String,
config: Option<serde_json::Value>,
) -> Result<Box<dyn OAuthProvider>> {
match provider_type {
"auth0" => {
let config = config.ok_or_else(|| crate::AuthError::ConfigError {
message: "Auth0 provider requires config with auth0_domain".to_string(),
})?;
let auth0_domain = config
.get("auth0_domain")
.and_then(|v| v.as_str())
.ok_or_else(|| crate::AuthError::ConfigError {
message: "Missing auth0_domain in config".to_string(),
})?
.to_string();
let provider =
Auth0OAuth::new(client_id, client_secret, auth0_domain, redirect_uri).await?;
Ok(Box::new(provider))
},
"github" => {
let provider = GitHubOAuth::new(client_id, client_secret, redirect_uri).await?;
Ok(Box::new(provider))
},
"google" => {
let provider = GoogleOAuth::new(client_id, client_secret, redirect_uri).await?;
Ok(Box::new(provider))
},
"keycloak" => {
let config = config.ok_or_else(|| crate::AuthError::ConfigError {
message: "Keycloak provider requires config with keycloak_url and realm"
.to_string(),
})?;
let keycloak_url = config
.get("keycloak_url")
.and_then(|v| v.as_str())
.ok_or_else(|| crate::AuthError::ConfigError {
message: "Missing keycloak_url in config".to_string(),
})?
.to_string();
let realm = config
.get("realm")
.and_then(|v| v.as_str())
.ok_or_else(|| crate::AuthError::ConfigError {
message: "Missing realm in config".to_string(),
})?
.to_string();
let provider =
KeycloakOAuth::new(client_id, client_secret, keycloak_url, realm, redirect_uri)
.await?;
Ok(Box::new(provider))
},
"okta" => {
let config = config.ok_or_else(|| crate::AuthError::ConfigError {
message: "Okta provider requires config with okta_domain".to_string(),
})?;
let okta_domain = config
.get("okta_domain")
.and_then(|v| v.as_str())
.ok_or_else(|| crate::AuthError::ConfigError {
message: "Missing okta_domain in config".to_string(),
})?
.to_string();
let provider =
OktaOAuth::new(client_id, client_secret, okta_domain, redirect_uri).await?;
Ok(Box::new(provider))
},
"azure_ad" => {
let config = config.ok_or_else(|| crate::AuthError::ConfigError {
message: "Azure AD provider requires config with tenant".to_string(),
})?;
let tenant = config
.get("tenant")
.and_then(|v| v.as_str())
.ok_or_else(|| crate::AuthError::ConfigError {
message: "Missing tenant in config".to_string(),
})?
.to_string();
let provider =
AzureADOAuth::new(client_id, client_secret, tenant, redirect_uri).await?;
Ok(Box::new(provider))
},
"ory" => {
let config = config.ok_or_else(|| crate::AuthError::ConfigError {
message: "Ory provider requires config with ory_issuer_url".to_string(),
})?;
let ory_issuer_url = config
.get("ory_issuer_url")
.and_then(|v| v.as_str())
.ok_or_else(|| crate::AuthError::ConfigError {
message: "Missing ory_issuer_url in config".to_string(),
})?
.to_string();
let provider =
OryOAuth::new(client_id, client_secret, ory_issuer_url, redirect_uri).await?;
Ok(Box::new(provider))
},
"logto" => {
let config = config.ok_or_else(|| crate::AuthError::ConfigError {
message: "Logto provider requires config with logto_endpoint".to_string(),
})?;
let logto_endpoint = config
.get("logto_endpoint")
.and_then(|v| v.as_str())
.ok_or_else(|| crate::AuthError::ConfigError {
message: "Missing logto_endpoint in config".to_string(),
})?
.to_string();
let provider =
LogtoOAuth::new(client_id, client_secret, logto_endpoint, redirect_uri).await?;
Ok(Box::new(provider))
},
_ => Err(crate::AuthError::ConfigError {
message: format!("Unknown provider type: {}", provider_type),
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_auth0_role_mapping() {
let roles = auth0::Auth0OAuth::map_auth0_roles_to_fraiseql(vec!["admin".to_string()]);
assert!(roles.contains(&"admin".to_string()));
}
#[test]
fn test_github_role_mapping() {
let roles = github::GitHubOAuth::map_teams_to_roles(vec![
"org:admin".to_string(),
"org:operator".to_string(),
]);
assert_eq!(roles.len(), 2);
}
#[test]
fn test_google_role_mapping() {
let roles = google::GoogleOAuth::map_groups_to_roles(vec![
"fraiseql-admins@company.com".to_string(),
]);
assert!(roles.contains(&"admin".to_string()));
}
#[test]
fn test_keycloak_role_mapping() {
let roles =
keycloak::KeycloakOAuth::map_keycloak_roles_to_fraiseql(vec!["admin".to_string()]);
assert!(roles.contains(&"admin".to_string()));
}
#[test]
fn test_okta_group_mapping() {
let groups = okta::OktaOAuth::map_okta_groups_to_fraiseql(vec![
"fraiseql-admin".to_string(),
"everyone".to_string(),
]);
assert_eq!(groups.len(), 2);
assert!(groups.contains(&"admin".to_string()));
assert!(groups.contains(&"viewer".to_string()));
}
#[test]
fn test_azure_ad_role_mapping() {
let roles =
azure_ad::AzureADOAuth::map_azure_roles_to_fraiseql(vec!["fraiseql.admin".to_string()]);
assert!(roles.contains(&"admin".to_string()));
}
#[test]
fn test_ory_group_mapping() {
let groups = ory::OryOAuth::map_ory_groups_to_fraiseql(vec![
"admin".to_string(),
"ory-operator".to_string(),
]);
assert_eq!(groups.len(), 2);
assert!(groups.contains(&"admin".to_string()));
assert!(groups.contains(&"operator".to_string()));
}
#[test]
fn test_logto_role_mapping() {
let roles = logto::LogtoOAuth::map_logto_roles_to_fraiseql(vec![
"admin".to_string(),
"logto-operator".to_string(),
]);
assert_eq!(roles.len(), 2);
assert!(roles.contains(&"admin".to_string()));
assert!(roles.contains(&"operator".to_string()));
}
}