use crate::{PluginCapabilities, PluginContext, PluginResult, Result};
use axum::http::{HeaderMap, Method, Uri};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[async_trait::async_trait]
pub trait AuthPlugin: Send + Sync {
fn capabilities(&self) -> PluginCapabilities;
async fn initialize(&self, config: &AuthPluginConfig) -> Result<()>;
async fn authenticate(
&self,
context: &PluginContext,
request: &AuthRequest,
config: &AuthPluginConfig,
) -> Result<PluginResult<AuthResponse>>;
fn validate_config(&self, config: &AuthPluginConfig) -> Result<()>;
fn supported_schemes(&self) -> Vec<String>;
async fn cleanup(&self) -> Result<()>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthPluginConfig {
pub config: HashMap<String, serde_json::Value>,
pub enabled: bool,
pub priority: i32,
pub settings: HashMap<String, serde_json::Value>,
}
impl Default for AuthPluginConfig {
fn default() -> Self {
Self {
config: HashMap::new(),
enabled: true,
priority: 100,
settings: HashMap::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct AuthRequest {
pub method: Method,
pub uri: Uri,
pub headers: HeaderMap,
pub body: Option<Vec<u8>>,
pub query_params: HashMap<String, String>,
pub client_ip: Option<String>,
pub user_agent: Option<String>,
pub timestamp: chrono::DateTime<chrono::Utc>,
}
impl AuthRequest {
pub fn from_axum(method: Method, uri: Uri, headers: HeaderMap, body: Option<Vec<u8>>) -> Self {
let query_params = uri
.query()
.map(|q| url::form_urlencoded::parse(q.as_bytes()).into_owned().collect())
.unwrap_or_default();
let client_ip = headers
.get("x-forwarded-for")
.or_else(|| headers.get("x-real-ip"))
.and_then(|h| h.to_str().ok())
.map(|s| s.to_string());
let user_agent =
headers.get("user-agent").and_then(|h| h.to_str().ok()).map(|s| s.to_string());
Self {
method,
uri,
headers,
body,
query_params,
client_ip,
user_agent,
timestamp: chrono::Utc::now(),
}
}
pub fn authorization_header(&self) -> Option<&str> {
self.headers.get("authorization").and_then(|h| h.to_str().ok())
}
pub fn bearer_token(&self) -> Option<&str> {
self.authorization_header().and_then(|auth| auth.strip_prefix("Bearer "))
}
pub fn basic_credentials(&self) -> Option<(String, String)> {
self.authorization_header()
.and_then(|auth| auth.strip_prefix("Basic "))
.and_then(|encoded| {
base64::Engine::decode(&base64::engine::general_purpose::STANDARD, encoded).ok()
})
.and_then(|decoded| String::from_utf8(decoded).ok())
.and_then(|creds| {
let parts: Vec<&str> = creds.splitn(2, ':').collect();
if parts.len() == 2 {
Some((parts[0].to_string(), parts[1].to_string()))
} else {
None
}
})
}
pub fn header(&self, name: &str) -> Option<&str> {
self.headers.get(name).and_then(|h| h.to_str().ok())
}
pub fn query_param(&self, name: &str) -> Option<&str> {
self.query_params.get(name).map(|s| s.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthResponse {
pub authenticated: bool,
pub identity: Option<UserIdentity>,
pub claims: HashMap<String, serde_json::Value>,
pub metadata: HashMap<String, serde_json::Value>,
pub error_message: Option<String>,
}
impl AuthResponse {
pub fn success(identity: UserIdentity, claims: HashMap<String, serde_json::Value>) -> Self {
Self {
authenticated: true,
identity: Some(identity),
claims,
metadata: HashMap::new(),
error_message: None,
}
}
pub fn failure<S: Into<String>>(error_message: S) -> Self {
Self {
authenticated: false,
identity: None,
claims: HashMap::new(),
metadata: HashMap::new(),
error_message: Some(error_message.into()),
}
}
pub fn with_metadata<S: Into<String>>(mut self, key: S, value: serde_json::Value) -> Self {
self.metadata.insert(key.into(), value);
self
}
pub fn is_authenticated(&self) -> bool {
self.authenticated
}
pub fn identity(&self) -> Option<&UserIdentity> {
self.identity.as_ref()
}
pub fn claims(&self) -> &HashMap<String, serde_json::Value> {
&self.claims
}
pub fn error_message(&self) -> Option<&str> {
self.error_message.as_deref()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserIdentity {
pub user_id: String,
pub username: Option<String>,
pub email: Option<String>,
pub display_name: Option<String>,
pub roles: Vec<String>,
pub groups: Vec<String>,
pub attributes: HashMap<String, serde_json::Value>,
}
impl UserIdentity {
pub fn new<S: Into<String>>(user_id: S) -> Self {
Self {
user_id: user_id.into(),
username: None,
email: None,
display_name: None,
roles: Vec::new(),
groups: Vec::new(),
attributes: HashMap::new(),
}
}
pub fn with_username<S: Into<String>>(mut self, username: S) -> Self {
self.username = Some(username.into());
self
}
pub fn with_email<S: Into<String>>(mut self, email: S) -> Self {
self.email = Some(email.into());
self
}
pub fn with_display_name<S: Into<String>>(mut self, display_name: S) -> Self {
self.display_name = Some(display_name.into());
self
}
pub fn with_role<S: Into<String>>(mut self, role: S) -> Self {
self.roles.push(role.into());
self
}
pub fn with_roles(mut self, roles: Vec<String>) -> Self {
self.roles.extend(roles);
self
}
pub fn with_group<S: Into<String>>(mut self, group: S) -> Self {
self.groups.push(group.into());
self
}
pub fn with_attribute<S: Into<String>>(mut self, key: S, value: serde_json::Value) -> Self {
self.attributes.insert(key.into(), value);
self
}
pub fn has_role(&self, role: &str) -> bool {
self.roles.iter().any(|r| r == role)
}
pub fn in_group(&self, group: &str) -> bool {
self.groups.iter().any(|g| g == group)
}
}
pub struct AuthPluginEntry {
pub plugin_id: crate::PluginId,
pub plugin: Box<dyn AuthPlugin>,
pub config: AuthPluginConfig,
pub capabilities: PluginCapabilities,
}
impl AuthPluginEntry {
pub fn new(
plugin_id: crate::PluginId,
plugin: Box<dyn AuthPlugin>,
config: AuthPluginConfig,
) -> Self {
let capabilities = plugin.capabilities();
Self {
plugin_id,
plugin,
config,
capabilities,
}
}
pub fn is_enabled(&self) -> bool {
self.config.enabled
}
pub fn priority(&self) -> i32 {
self.config.priority
}
}
pub trait AuthPluginFactory: Send + Sync {
fn create_plugin(&self) -> Result<Box<dyn AuthPlugin>>;
}
#[cfg(test)]
mod tests {
#[test]
fn test_module_compiles() {
}
}