use super::errors::SecurityError;
use super::types::*;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
#[async_trait]
pub trait AuthProvider: Send + Sync {
async fn authenticate(
&self,
credentials: &Credentials,
) -> Result<AuthenticationResult, SecurityError>;
async fn validate_token(&self, token: &str) -> Result<User, SecurityError>;
async fn refresh_token(
&self,
refresh_token: &str,
) -> Result<AuthenticationResult, SecurityError>;
async fn logout(&self, token: &str) -> Result<(), SecurityError>;
fn provider_type(&self) -> &'static str;
}
pub struct OAuth2Provider {
client_id: String,
client_secret: String,
authorization_url: String,
token_url: String,
userinfo_url: String,
redirect_uri: String,
scope: Vec<String>,
token_cache: Arc<RwLock<HashMap<String, (User, u64)>>>, }
impl OAuth2Provider {
pub fn new(
client_id: String,
client_secret: String,
authorization_url: String,
token_url: String,
userinfo_url: String,
redirect_uri: String,
) -> Self {
Self {
client_id,
client_secret,
authorization_url,
token_url,
userinfo_url,
redirect_uri,
scope: vec![
"openid".to_string(),
"profile".to_string(),
"email".to_string(),
],
token_cache: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn get_authorization_url(&self, state: &str) -> String {
format!(
"{}?client_id={}&response_type=code&redirect_uri={}&scope={}&state={}",
self.authorization_url,
self.client_id,
urlencoding::encode(&self.redirect_uri),
self.scope.join(" "),
state
)
}
pub async fn exchange_code_for_token(
&self,
code: &str,
) -> Result<OAuth2TokenResponse, SecurityError> {
Ok(OAuth2TokenResponse {
access_token: format!("access_token_{}", code),
token_type: "Bearer".to_string(),
expires_in: 3600,
refresh_token: Some(format!("refresh_token_{}", code)),
scope: Some(self.scope.join(" ")),
})
}
pub async fn get_user_info(&self, _access_token: &str) -> Result<OAuth2UserInfo, SecurityError> {
Ok(OAuth2UserInfo {
sub: "user123".to_string(),
name: "Test User".to_string(),
email: "test@example.com".to_string(),
email_verified: true,
picture: Some("https://example.com/avatar.jpg".to_string()),
})
}
pub async fn refresh_access_token(
&self,
refresh_token: &str,
) -> Result<OAuth2TokenResponse, SecurityError> {
Ok(OAuth2TokenResponse {
access_token: format!("new_access_token_{}", refresh_token),
token_type: "Bearer".to_string(),
expires_in: 3600,
refresh_token: Some(format!("new_refresh_token_{}", refresh_token)),
scope: Some(self.scope.join(" ")),
})
}
}
#[async_trait]
impl AuthProvider for OAuth2Provider {
async fn authenticate(
&self,
credentials: &Credentials,
) -> Result<AuthenticationResult, SecurityError> {
match &credentials.credential_type {
CredentialType::OAuth2 => {
if let Some(code) = credentials.get_data("code") {
let token_response = self.exchange_code_for_token(code).await?;
let user_info = self.get_user_info(&token_response.access_token).await?;
let user = User::new(
user_info.sub,
user_info.name.clone(),
user_info.email,
user_info.name,
);
let expires_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
+ token_response.expires_in as u64;
{
let mut cache = self.token_cache.write().await;
cache.insert(
token_response.access_token.clone(),
(user.clone(), expires_at),
);
}
Ok(AuthenticationResult::success(
user,
token_response.access_token,
expires_at,
))
} else {
Err(SecurityError::authentication_failed(
"Missing authorization code",
))
}
}
_ => Err(SecurityError::authentication_failed(
"Invalid credential type for OAuth2",
)),
}
}
async fn validate_token(&self, token: &str) -> Result<User, SecurityError> {
let cache = self.token_cache.read().await;
if let Some((user, expires_at)) = cache.get(token) {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
if *expires_at > now {
Ok(user.clone())
} else {
Err(SecurityError::token_error("Token expired"))
}
} else {
Err(SecurityError::token_error("Invalid token"))
}
}
async fn refresh_token(
&self,
refresh_token: &str,
) -> Result<AuthenticationResult, SecurityError> {
let token_response = self.refresh_access_token(refresh_token).await?;
let user_info = self.get_user_info(&token_response.access_token).await?;
let user = User::new(
user_info.sub,
user_info.name.clone(),
user_info.email,
user_info.name,
);
let expires_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
+ token_response.expires_in as u64;
{
let mut cache = self.token_cache.write().await;
cache.insert(
token_response.access_token.clone(),
(user.clone(), expires_at),
);
}
Ok(AuthenticationResult::success(
user,
token_response.access_token,
expires_at,
))
}
async fn logout(&self, token: &str) -> Result<(), SecurityError> {
let mut cache = self.token_cache.write().await;
cache.remove(token);
Ok(())
}
fn provider_type(&self) -> &'static str {
"oauth2"
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAuth2TokenResponse {
pub access_token: String,
pub token_type: String,
pub expires_in: u32,
pub refresh_token: Option<String>,
pub scope: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAuth2UserInfo {
pub sub: String,
pub name: String,
pub email: String,
pub email_verified: bool,
pub picture: Option<String>,
}
pub struct SAMLProvider {
entity_id: String,
sso_url: String,
slo_url: String,
certificate: String,
private_key: String,
user_cache: Arc<RwLock<HashMap<String, User>>>, }
impl SAMLProvider {
pub fn new(
entity_id: String,
sso_url: String,
slo_url: String,
certificate: String,
private_key: String,
) -> Self {
Self {
entity_id,
sso_url,
slo_url,
certificate,
private_key,
user_cache: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn get_sso_url(&self, relay_state: &str) -> String {
format!(
"{}?SAMLRequest={}&RelayState={}",
self.sso_url,
urlencoding::encode(&self.create_authn_request()),
urlencoding::encode(relay_state)
)
}
fn create_authn_request(&self) -> String {
format!("<samlp:AuthnRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" ID=\"_{}\" Version=\"2.0\" IssueInstant=\"{}\" Destination=\"{}\" AssertionConsumerServiceURL=\"{}\"><saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">{}</saml:Issuer></samlp:AuthnRequest>",
uuid::Uuid::new_v4(),
chrono::Utc::now().to_rfc3339(),
self.sso_url,
self.sso_url,
self.entity_id)
}
pub async fn process_saml_response(&self, _saml_response: &str) -> Result<User, SecurityError> {
let user = User::new(
"saml_user_123".to_string(),
"saml_user".to_string(),
"saml@example.com".to_string(),
"SAML User".to_string(),
);
{
let mut cache = self.user_cache.write().await;
cache.insert("assertion_123".to_string(), user.clone());
}
Ok(user)
}
}
#[async_trait]
impl AuthProvider for SAMLProvider {
async fn authenticate(
&self,
credentials: &Credentials,
) -> Result<AuthenticationResult, SecurityError> {
match &credentials.credential_type {
CredentialType::SAML => {
if let Some(saml_response) = credentials.get_data("saml_response") {
let user = self.process_saml_response(saml_response).await?;
let token = format!("saml_token_{}", uuid::Uuid::new_v4());
let expires_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
+ 3600;
Ok(AuthenticationResult::success(user, token, expires_at))
} else {
Err(SecurityError::authentication_failed(
"Missing SAML response",
))
}
}
_ => Err(SecurityError::authentication_failed(
"Invalid credential type for SAML",
)),
}
}
async fn validate_token(&self, token: &str) -> Result<User, SecurityError> {
if token.starts_with("saml_token_") {
Ok(User::new(
"saml_user_123".to_string(),
"saml_user".to_string(),
"saml@example.com".to_string(),
"SAML User".to_string(),
))
} else {
Err(SecurityError::token_error("Invalid SAML token"))
}
}
async fn refresh_token(
&self,
_refresh_token: &str,
) -> Result<AuthenticationResult, SecurityError> {
Err(SecurityError::token_error(
"SAML tokens cannot be refreshed",
))
}
async fn logout(&self, _token: &str) -> Result<(), SecurityError> {
Ok(())
}
fn provider_type(&self) -> &'static str {
"saml"
}
}
pub struct UsernamePasswordProvider {
users: Arc<RwLock<HashMap<String, (String, User)>>>, }
impl UsernamePasswordProvider {
pub fn new() -> Self {
Self {
users: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn add_user(&self, username: String, password: String, user: User) {
let password_hash = self.hash_password(&password);
let mut users = self.users.write().await;
users.insert(username, (password_hash, user));
}
fn hash_password(&self, password: &str) -> String {
format!("hash_{}", password)
}
fn verify_password(&self, password: &str, hash: &str) -> bool {
hash == &format!("hash_{}", password)
}
}
#[async_trait]
impl AuthProvider for UsernamePasswordProvider {
async fn authenticate(
&self,
credentials: &Credentials,
) -> Result<AuthenticationResult, SecurityError> {
match &credentials.credential_type {
CredentialType::UsernamePassword => {
if let (Some(username), Some(password)) =
(&credentials.username, &credentials.password)
{
let users = self.users.read().await;
if let Some((password_hash, user)) = users.get(username) {
if self.verify_password(password, password_hash) {
let token = format!("token_{}", uuid::Uuid::new_v4());
let expires_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
+ 3600;
Ok(AuthenticationResult::success(
user.clone(),
token,
expires_at,
))
} else {
Err(SecurityError::authentication_failed("Invalid password"))
}
} else {
Err(SecurityError::authentication_failed("User not found"))
}
} else {
Err(SecurityError::authentication_failed(
"Missing username or password",
))
}
}
_ => Err(SecurityError::authentication_failed(
"Invalid credential type for username/password",
)),
}
}
async fn validate_token(&self, token: &str) -> Result<User, SecurityError> {
if token.starts_with("token_") {
Ok(User::new(
"user123".to_string(),
"testuser".to_string(),
"test@example.com".to_string(),
"Test User".to_string(),
))
} else {
Err(SecurityError::token_error("Invalid token"))
}
}
async fn refresh_token(
&self,
_refresh_token: &str,
) -> Result<AuthenticationResult, SecurityError> {
Err(SecurityError::token_error("Token refresh not supported"))
}
async fn logout(&self, _token: &str) -> Result<(), SecurityError> {
Ok(())
}
fn provider_type(&self) -> &'static str {
"username_password"
}
}