use crate::{
authentication::credentials::{Credential, CredentialMetadata},
errors::{AuthError, Result},
tokens::AuthToken,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub mod enhanced_device;
pub mod hardware_token;
pub mod passkey;
#[cfg(feature = "saml")]
pub mod saml;
#[cfg(feature = "enhanced-device-flow")]
pub use enhanced_device::EnhancedDeviceFlowMethod;
pub use hardware_token::HardwareToken;
#[cfg(feature = "passkeys")]
pub use passkey::PasskeyAuthMethod;
#[cfg(feature = "saml")]
pub use saml::SamlAuthMethod;
#[derive(Debug, Clone)]
pub enum MethodResult {
Success(Box<AuthToken>),
MfaRequired(Box<MfaChallenge>),
Failure { reason: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MfaChallenge {
pub id: String,
pub mfa_type: MfaType,
pub user_id: String,
pub expires_at: chrono::DateTime<chrono::Utc>,
pub message: Option<String>,
pub data: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MfaType {
Totp,
Sms { phone_number: String },
Email { email_address: String },
Push { device_id: String },
SecurityKey,
BackupCode,
}
pub trait AuthMethod: Send + Sync {
type MethodResult: Send + Sync + 'static;
type AuthToken: Send + Sync + 'static;
fn name(&self) -> &str;
fn authenticate(
&self,
credential: Credential,
metadata: CredentialMetadata,
) -> impl std::future::Future<Output = Result<Self::MethodResult>> + Send;
fn validate_config(&self) -> Result<()>;
fn supports_refresh(&self) -> bool {
false
}
fn refresh_token(
&self,
_refresh_token: String,
) -> impl std::future::Future<Output = Result<AuthToken, AuthError>> + Send {
async {
Err(AuthError::auth_method(
self.name(),
"Token refresh not supported by this method".to_string(),
))
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserInfo {
pub id: String,
pub username: String,
pub email: Option<String>,
pub name: Option<String>,
pub roles: Vec<String>,
pub active: bool,
pub attributes: HashMap<String, serde_json::Value>,
}
pub enum AuthMethodEnum {
Password(PasswordMethod),
Jwt(JwtMethod),
ApiKey(ApiKeyMethod),
OAuth2(OAuth2Method),
#[cfg(feature = "saml")]
Saml(SamlAuthMethod),
#[cfg(feature = "ldap-auth")]
Ldap(LdapAuthMethod),
HardwareToken(HardwareToken),
OpenIdConnect(OpenIdConnectAuthMethod),
AdvancedMfa(AdvancedMfaAuthMethod),
#[cfg(feature = "enhanced-device-flow")]
EnhancedDeviceFlow(Box<enhanced_device::EnhancedDeviceFlowMethod>),
#[cfg(feature = "passkeys")]
Passkey(PasskeyAuthMethod),
}
#[derive(Debug)]
pub struct PasswordMethod;
#[derive(Debug)]
pub struct JwtMethod;
#[derive(Debug)]
pub struct ApiKeyMethod;
#[derive(Debug)]
pub struct OAuth2Method;
#[cfg(feature = "ldap-auth")]
#[derive(Debug)]
pub struct LdapAuthMethod;
#[derive(Debug)]
pub struct OpenIdConnectAuthMethod;
#[derive(Debug)]
pub struct AdvancedMfaAuthMethod;
impl Default for PasswordMethod {
fn default() -> Self {
Self::new()
}
}
impl PasswordMethod {
pub fn new() -> Self {
Self
}
}
impl Default for JwtMethod {
fn default() -> Self {
Self::new()
}
}
impl JwtMethod {
pub fn new() -> Self {
Self
}
pub fn secret_key(self, _secret: &str) -> Self {
self
}
pub fn issuer(self, _issuer: &str) -> Self {
self
}
pub fn audience(self, _audience: &str) -> Self {
self
}
}
impl Default for ApiKeyMethod {
fn default() -> Self {
Self::new()
}
}
impl ApiKeyMethod {
pub fn new() -> Self {
Self
}
}
impl Default for OAuth2Method {
fn default() -> Self {
Self::new()
}
}
impl OAuth2Method {
pub fn new() -> Self {
Self
}
}
impl AuthMethod for AuthMethodEnum {
type MethodResult = MethodResult;
type AuthToken = AuthToken;
fn name(&self) -> &str {
match self {
AuthMethodEnum::Password(_) => "password",
AuthMethodEnum::Jwt(_) => "jwt",
AuthMethodEnum::ApiKey(_) => "api_key",
AuthMethodEnum::OAuth2(_) => "oauth2",
#[cfg(feature = "saml")]
AuthMethodEnum::Saml(_) => "saml",
#[cfg(feature = "ldap-auth")]
AuthMethodEnum::Ldap(_) => "ldap",
AuthMethodEnum::HardwareToken(_) => "hardware_token",
AuthMethodEnum::OpenIdConnect(_) => "openid_connect",
AuthMethodEnum::AdvancedMfa(_) => "advanced_mfa",
#[cfg(feature = "enhanced-device-flow")]
AuthMethodEnum::EnhancedDeviceFlow(_) => "enhanced_device_flow",
#[cfg(feature = "passkeys")]
AuthMethodEnum::Passkey(_) => "passkey",
}
}
async fn authenticate(
&self,
credential: Credential,
metadata: CredentialMetadata,
) -> Result<Self::MethodResult> {
match &credential {
Credential::Password { username, password } => {
if username.is_empty() || password.is_empty() {
return Ok(MethodResult::Failure {
reason: "Username or password cannot be empty".to_string(),
});
}
}
Credential::Jwt { token } => {
if token.is_empty() {
return Ok(MethodResult::Failure {
reason: "JWT token cannot be empty".to_string(),
});
}
}
Credential::ApiKey { key } => {
if key.is_empty() {
return Ok(MethodResult::Failure {
reason: "API key cannot be empty".to_string(),
});
}
}
_ => {
}
}
if let Some(ip) = &metadata.client_ip
&& ip.starts_with("127.")
{
tracing::warn!("Authentication attempt from localhost");
}
tracing::warn!(
"Using default authentication method - this should not happen in production"
);
Ok(MethodResult::Failure {
reason:
"Authentication method not fully implemented - please use a concrete implementation"
.to_string(),
})
}
fn validate_config(&self) -> Result<()> {
Ok(())
}
fn supports_refresh(&self) -> bool {
false
}
async fn refresh_token(&self, _refresh_token: String) -> Result<AuthToken, AuthError> {
Err(AuthError::auth_method(
self.name(),
"Token refresh not supported by this method".to_string(),
))
}
}
impl MfaChallenge {
pub fn new(
mfa_type: MfaType,
user_id: impl Into<String>,
expires_in: std::time::Duration,
) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
mfa_type,
user_id: user_id.into(),
expires_at: chrono::Utc::now() + chrono::Duration::from_std(expires_in).unwrap(),
message: None,
data: HashMap::new(),
}
}
pub fn id(&self) -> &str {
&self.id
}
pub fn is_expired(&self) -> bool {
chrono::Utc::now() > self.expires_at
}
pub fn with_message(mut self, message: impl Into<String>) -> Self {
self.message = Some(message.into());
self
}
}