/// Authentication provider trait. /// /// Enables pluggable authentication backends (Authentik, custom RBAC, Keycloak, etc). /// Implementations must validate tokens and extract claims. /// /// # Minimal Design /// Single method: validate_token() returns raw claims JSON. /// Memory service extracts what it needs (groups, resources, etc). /// This works with ANY JSON structure. use async_trait::async_trait; use serde_json::Value; /// Standard token claims format. #[derive(Clone, Debug)] pub struct Claims { /// Subject (user/service ID) pub sub: String, /// Groups/roles user belongs to pub groups: Vec, /// Custom attributes (memory_resources, etc) pub attributes: serde_json::Map, /// Expiration timestamp (Unix seconds) pub exp: i64, /// Issued at timestamp (Unix seconds) pub iat: i64, } /// Authentication provider trait. /// /// Implement this trait for any OIDC/OAuth2 provider or custom auth system. #[async_trait] pub trait AuthProvider: Send + Sync { /// Validate token and extract claims. /// /// Implementation should: /// 1. Verify JWT signature (using JWKS or shared key) /// 2. Check expiration /// 3. Validate issuer and audience /// 4. Extract claims into standard Claims format /// /// # Errors /// Returns error if token is invalid, expired, or verification fails. async fn validate_token(&self, token: &str) -> Result; } /// Authentication errors. #[derive(Clone, Debug)] pub enum AuthError { /// Token is missing or malformed MissingToken, /// JWT signature verification failed InvalidSignature, /// Token has expired TokenExpired, /// Issuer claim doesn't match configured issuer InvalidIssuer, /// Audience claim doesn't match configured audience InvalidAudience, /// Can't reach OIDC provider ProviderUnavailable(String), /// Other error Other(String), } impl std::fmt::Display for AuthError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { AuthError::MissingToken => write!(f, "Missing token"), AuthError::InvalidSignature => write!(f, "Invalid signature"), AuthError::TokenExpired => write!(f, "Token expired"), AuthError::InvalidIssuer => write!(f, "Invalid issuer"), AuthError::InvalidAudience => write!(f, "Invalid audience"), AuthError::ProviderUnavailable(e) => write!(f, "Provider unavailable: {}", e), AuthError::Other(e) => write!(f, "{}", e), } } } impl std::error::Error for AuthError {} #[cfg(test)] mod tests { use super::*; #[test] fn test_auth_error_display() { let err = AuthError::TokenExpired; assert_eq!(err.to_string(), "Token expired"); } #[test] fn test_claims_structure() { let claims = Claims { sub: "rock".to_string(), groups: vec!["memory-users".to_string()], attributes: serde_json::Map::new(), exp: 1735689600, iat: 1735689300, }; assert_eq!(claims.sub, "rock"); assert_eq!(claims.groups.len(), 1); } }