//! Authentication provider trait and implementations //! //! Defines a unified interface for different authentication methods. use async_trait::async_trait; use std::sync::Arc; use iam_types::{ AuthMethod, Error, IamError, InternalTokenClaims, JwtClaims, PrincipalRef, Result, }; use crate::jwt::JwtVerifier; use crate::mtls::{CertificateInfo, MtlsVerifier}; use crate::token::InternalTokenService; /// Result of authentication #[derive(Debug, Clone)] pub struct AuthnResult { /// Authenticated principal reference pub principal_ref: PrincipalRef, /// Authentication method used pub auth_method: AuthMethod, /// Node ID (for service accounts) pub node_id: Option, /// Organization ID pub org_id: Option, /// Project ID pub project_id: Option, /// Groups (from JWT) pub groups: Vec, /// Original claims (if JWT) pub jwt_claims: Option, /// Internal token claims (if internal token) pub internal_claims: Option, } /// Authentication credentials #[derive(Debug, Clone)] pub enum AuthnCredentials { /// Bearer token (JWT or internal token) BearerToken(String), /// mTLS certificate info Certificate(CertificateInfo), /// API key ApiKey(String), } /// Authentication provider trait #[async_trait] pub trait AuthnProvider: Send + Sync { /// Authenticate using the provided credentials async fn authenticate(&self, credentials: &AuthnCredentials) -> Result; } /// Combined authentication provider supporting multiple methods pub struct CombinedAuthProvider { jwt_verifier: Option>, mtls_verifier: Option>, internal_token_service: Option>, } impl CombinedAuthProvider { /// Create a new combined auth provider pub fn new() -> Self { Self { jwt_verifier: None, mtls_verifier: None, internal_token_service: None, } } /// Add JWT verifier pub fn with_jwt(mut self, verifier: JwtVerifier) -> Self { self.jwt_verifier = Some(Arc::new(verifier)); self } /// Add mTLS verifier pub fn with_mtls(mut self, verifier: MtlsVerifier) -> Self { self.mtls_verifier = Some(Arc::new(verifier)); self } /// Add internal token service pub fn with_internal_token(mut self, service: InternalTokenService) -> Self { self.internal_token_service = Some(Arc::new(service)); self } /// Authenticate a bearer token async fn authenticate_bearer(&self, token: &str) -> Result { // Try internal token first (faster, local verification) if let Some(internal_service) = &self.internal_token_service { if let Ok(claims) = internal_service.verify(token).await { return Ok(AuthnResult { principal_ref: PrincipalRef::new( claims.principal_kind.clone(), &claims.principal_id, ), auth_method: AuthMethod::Internal, node_id: claims.node_id.clone(), org_id: claims.org_id.clone(), project_id: claims.project_id.clone(), groups: vec![], jwt_claims: None, internal_claims: Some(claims), }); } } // Try JWT verification if let Some(jwt_verifier) = &self.jwt_verifier { let claims = jwt_verifier.verify(token).await?; // Map JWT sub to principal let principal_ref = PrincipalRef::user(&claims.sub); return Ok(AuthnResult { principal_ref, auth_method: AuthMethod::Jwt, node_id: None, org_id: claims.org_id.clone(), project_id: claims.project_id.clone(), groups: claims.groups.clone(), jwt_claims: Some(claims), internal_claims: None, }); } Err(Error::Iam(IamError::AuthnFailed( "No authentication provider configured for bearer tokens".into(), ))) } /// Authenticate using certificate fn authenticate_certificate(&self, cert_info: &CertificateInfo) -> Result { let mtls_verifier = self.mtls_verifier.as_ref().ok_or_else(|| { Error::Iam(IamError::AuthnFailed( "mTLS authentication not configured".into(), )) })?; let result = mtls_verifier.verify(cert_info)?; Ok(AuthnResult { principal_ref: result.principal_ref, auth_method: AuthMethod::Mtls, node_id: result.node_id, org_id: None, project_id: None, groups: vec![], jwt_claims: None, internal_claims: None, }) } /// Authenticate using API key async fn authenticate_api_key(&self, _api_key: &str) -> Result { // API key authentication would typically: // 1. Look up the API key in the store // 2. Verify it's valid and not expired // 3. Return the associated principal // For now, this is a stub Err(Error::Iam(IamError::AuthnFailed( "API key authentication not yet implemented".into(), ))) } } impl Default for CombinedAuthProvider { fn default() -> Self { Self::new() } } #[async_trait] impl AuthnProvider for CombinedAuthProvider { async fn authenticate(&self, credentials: &AuthnCredentials) -> Result { match credentials { AuthnCredentials::BearerToken(token) => self.authenticate_bearer(token).await, AuthnCredentials::Certificate(cert_info) => self.authenticate_certificate(cert_info), AuthnCredentials::ApiKey(key) => self.authenticate_api_key(key).await, } } } /// Extract authentication credentials from HTTP headers pub fn extract_credentials_from_headers( authorization: Option<&str>, cert_info: Option, ) -> Option { // Check for mTLS first (if certificate is provided) if let Some(cert) = cert_info { return Some(AuthnCredentials::Certificate(cert)); } // Check Authorization header if let Some(auth_header) = authorization { if let Some(token) = auth_header.strip_prefix("Bearer ") { return Some(AuthnCredentials::BearerToken(token.to_string())); } if let Some(key) = auth_header.strip_prefix("ApiKey ") { return Some(AuthnCredentials::ApiKey(key.to_string())); } } None } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_combined_provider_no_config() { let provider = CombinedAuthProvider::new(); let result = provider .authenticate(&AuthnCredentials::BearerToken("some-token".into())) .await; assert!(result.is_err()); } #[test] fn test_extract_bearer_token() { let creds = extract_credentials_from_headers(Some("Bearer abc123"), None); assert!(matches!( creds, Some(AuthnCredentials::BearerToken(t)) if t == "abc123" )); } #[test] fn test_extract_api_key() { let creds = extract_credentials_from_headers(Some("ApiKey secret-key"), None); assert!(matches!( creds, Some(AuthnCredentials::ApiKey(k)) if k == "secret-key" )); } }