use std::{future::Future, pin::Pin, sync::Arc};
use crate::error::{KalamLinkError, Result};
#[derive(Debug, Clone)]
pub enum AuthProvider {
BasicAuth(String, String),
JwtToken(String),
None,
}
impl AuthProvider {
pub fn basic_auth(user: String, password: String) -> Self {
Self::BasicAuth(user, password)
}
pub fn system_user_auth(password: String) -> Self {
Self::BasicAuth("root".to_string(), password)
}
pub fn jwt_token(token: String) -> Self {
Self::JwtToken(token)
}
pub fn none() -> Self {
Self::None
}
pub fn apply_to_request(
&self,
request: reqwest::RequestBuilder,
) -> Result<reqwest::RequestBuilder> {
match self {
Self::BasicAuth(_, _) => Err(KalamLinkError::AuthenticationError(
"User/password credentials can only be used with /v1/api/auth/login; exchange \
them for a JWT before sending authenticated requests."
.to_string(),
)),
Self::JwtToken(token) => Ok(request.bearer_auth(token)),
Self::None => Ok(request),
}
}
pub fn is_authenticated(&self) -> bool {
!matches!(self, Self::None)
}
}
pub trait DynamicAuthProvider: Send + Sync + 'static {
fn get_auth(&self) -> Pin<Box<dyn Future<Output = Result<AuthProvider>> + Send + '_>>;
}
pub type ArcDynAuthProvider = Arc<dyn DynamicAuthProvider>;
#[derive(Clone)]
pub enum ResolvedAuth {
Static(AuthProvider),
Dynamic(ArcDynAuthProvider),
}
impl ResolvedAuth {
pub async fn resolve(&self) -> Result<AuthProvider> {
match self {
Self::Static(p) => Ok(p.clone()),
Self::Dynamic(provider) => provider.get_auth().await,
}
}
pub fn is_none(&self) -> bool {
matches!(self, Self::Static(AuthProvider::None))
}
}
impl std::fmt::Debug for ResolvedAuth {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Static(p) => write!(f, "ResolvedAuth::Static({:?})", p),
Self::Dynamic(_) => write!(f, "ResolvedAuth::Dynamic(<fn>)"),
}
}
}
impl Default for ResolvedAuth {
fn default() -> Self {
Self::Static(AuthProvider::None)
}
}
impl From<AuthProvider> for ResolvedAuth {
fn from(p: AuthProvider) -> Self {
Self::Static(p)
}
}
impl From<ArcDynAuthProvider> for ResolvedAuth {
fn from(p: ArcDynAuthProvider) -> Self {
Self::Dynamic(p)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_auth_provider_creation() {
let basic = AuthProvider::basic_auth("alice".to_string(), "secret".to_string());
assert!(basic.is_authenticated());
let system = AuthProvider::system_user_auth("password123".to_string());
assert!(system.is_authenticated());
let jwt = AuthProvider::jwt_token("test_token".to_string());
assert!(jwt.is_authenticated());
let none = AuthProvider::none();
assert!(!none.is_authenticated());
}
#[test]
fn test_apply_to_request_rejects_basic_auth() {
let auth = AuthProvider::basic_auth("alice".to_string(), "secret123".to_string());
let client = reqwest::Client::new();
let request = client.get("http://localhost:2900");
let result = auth.apply_to_request(request);
assert!(result.is_err());
}
#[test]
fn test_system_user_auth_uses_root() {
let auth = AuthProvider::system_user_auth("test_password".to_string());
match auth {
AuthProvider::BasicAuth(user, password) => {
assert_eq!(user, "root");
assert_eq!(password, "test_password");
},
_ => panic!("Expected BasicAuth variant"),
}
}
}