use crate::error::{LettaError, LettaResult};
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION};
use std::fmt;
#[derive(Clone, Debug)]
pub enum AuthConfig {
None,
Bearer {
token: String,
},
}
impl AuthConfig {
pub fn bearer(token: impl Into<String>) -> Self {
Self::Bearer {
token: token.into(),
}
}
pub fn none() -> Self {
Self::None
}
pub fn apply_to_headers(&self, headers: &mut HeaderMap) -> LettaResult<()> {
match self {
Self::None => {
}
Self::Bearer { token } => {
let auth_value = format!("Bearer {token}");
let header_value = HeaderValue::from_str(&auth_value)
.map_err(|_| LettaError::auth("Invalid bearer token format"))?;
headers.insert(AUTHORIZATION, header_value);
}
}
Ok(())
}
pub fn is_authenticated(&self) -> bool {
matches!(self, Self::Bearer { .. })
}
pub fn auth_type(&self) -> &'static str {
match self {
Self::None => "none",
Self::Bearer { .. } => "bearer",
}
}
pub fn validate(&self) -> LettaResult<()> {
match self {
Self::None => Ok(()),
Self::Bearer { token } => {
if token.trim().is_empty() {
return Err(LettaError::auth("Bearer token cannot be empty"));
}
if token.contains('\n') || token.contains('\r') {
return Err(LettaError::auth("Bearer token cannot contain newlines"));
}
if token.len() > 1024 {
return Err(LettaError::auth(
"Bearer token is too long (max 1024 characters)",
));
}
Ok(())
}
}
}
}
impl fmt::Display for AuthConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "No authentication"),
Self::Bearer { .. } => write!(f, "Bearer token authentication"),
}
}
}
impl Default for AuthConfig {
fn default() -> Self {
Self::None
}
}
pub fn from_env() -> Option<AuthConfig> {
let env_vars = ["LETTA_API_KEY", "LETTA_TOKEN", "LETTA_AUTH_TOKEN"];
for var in &env_vars {
if let Ok(token) = std::env::var(var) {
if !token.trim().is_empty() {
return Some(AuthConfig::bearer(token.trim()));
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use reqwest::header::HeaderMap;
#[test]
fn test_auth_config_creation() {
let auth = AuthConfig::bearer("test-token");
assert!(auth.is_authenticated());
assert_eq!(auth.auth_type(), "bearer");
let auth = AuthConfig::none();
assert!(!auth.is_authenticated());
assert_eq!(auth.auth_type(), "none");
}
#[test]
fn test_apply_to_headers() {
let mut headers = HeaderMap::new();
let auth = AuthConfig::bearer("test-token");
auth.apply_to_headers(&mut headers).unwrap();
let auth_header = headers.get(AUTHORIZATION).unwrap();
assert_eq!(auth_header.to_str().unwrap(), "Bearer test-token");
let mut headers = HeaderMap::new();
let auth = AuthConfig::none();
auth.apply_to_headers(&mut headers).unwrap();
assert!(!headers.contains_key(AUTHORIZATION));
}
#[test]
fn test_validation() {
let auth = AuthConfig::bearer("valid-token");
assert!(auth.validate().is_ok());
let auth = AuthConfig::bearer("");
assert!(auth.validate().is_err());
let auth = AuthConfig::bearer("token\nwith\nnewlines");
assert!(auth.validate().is_err());
let long_token = "a".repeat(1025);
let auth = AuthConfig::bearer(long_token);
assert!(auth.validate().is_err());
let auth = AuthConfig::none();
assert!(auth.validate().is_ok());
}
#[test]
fn test_display() {
let auth = AuthConfig::bearer("token");
assert_eq!(auth.to_string(), "Bearer token authentication");
let auth = AuthConfig::none();
assert_eq!(auth.to_string(), "No authentication");
}
#[test]
fn test_from_env() {
let auth = from_env();
assert!(auth.is_none() || auth.is_some());
}
}