feat(rustproxy): introduce a Rust-powered proxy engine and workspace with core crates for proxy functionality, ACME/TLS support, passthrough and HTTP proxies, metrics, nftables integration, routing/security, management IPC, tests, and README updates
This commit is contained in:
111
rust/crates/rustproxy-security/src/basic_auth.rs
Normal file
111
rust/crates/rustproxy-security/src/basic_auth.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose::STANDARD as BASE64;
|
||||
|
||||
/// Basic auth validator.
|
||||
pub struct BasicAuthValidator {
|
||||
users: Vec<(String, String)>,
|
||||
realm: String,
|
||||
}
|
||||
|
||||
impl BasicAuthValidator {
|
||||
pub fn new(users: Vec<(String, String)>, realm: Option<String>) -> Self {
|
||||
Self {
|
||||
users,
|
||||
realm: realm.unwrap_or_else(|| "Restricted".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate an Authorization header value.
|
||||
/// Returns the username if valid.
|
||||
pub fn validate(&self, auth_header: &str) -> Option<String> {
|
||||
let auth_header = auth_header.trim();
|
||||
if !auth_header.starts_with("Basic ") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let encoded = &auth_header[6..];
|
||||
let decoded = BASE64.decode(encoded).ok()?;
|
||||
let credentials = String::from_utf8(decoded).ok()?;
|
||||
|
||||
let mut parts = credentials.splitn(2, ':');
|
||||
let username = parts.next()?;
|
||||
let password = parts.next()?;
|
||||
|
||||
for (u, p) in &self.users {
|
||||
if u == username && p == password {
|
||||
return Some(username.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the realm for WWW-Authenticate header.
|
||||
pub fn realm(&self) -> &str {
|
||||
&self.realm
|
||||
}
|
||||
|
||||
/// Generate the WWW-Authenticate header value.
|
||||
pub fn www_authenticate(&self) -> String {
|
||||
format!("Basic realm=\"{}\"", self.realm)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use base64::Engine;
|
||||
|
||||
fn make_validator() -> BasicAuthValidator {
|
||||
BasicAuthValidator::new(
|
||||
vec![
|
||||
("admin".to_string(), "secret".to_string()),
|
||||
("user".to_string(), "pass".to_string()),
|
||||
],
|
||||
Some("TestRealm".to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
fn encode_basic(user: &str, pass: &str) -> String {
|
||||
let encoded = BASE64.encode(format!("{}:{}", user, pass));
|
||||
format!("Basic {}", encoded)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_credentials() {
|
||||
let validator = make_validator();
|
||||
let header = encode_basic("admin", "secret");
|
||||
assert_eq!(validator.validate(&header), Some("admin".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_password() {
|
||||
let validator = make_validator();
|
||||
let header = encode_basic("admin", "wrong");
|
||||
assert_eq!(validator.validate(&header), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_basic_scheme() {
|
||||
let validator = make_validator();
|
||||
assert_eq!(validator.validate("Bearer sometoken"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_malformed_base64() {
|
||||
let validator = make_validator();
|
||||
assert_eq!(validator.validate("Basic !!!not-base64!!!"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_www_authenticate_format() {
|
||||
let validator = make_validator();
|
||||
assert_eq!(validator.www_authenticate(), "Basic realm=\"TestRealm\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_realm() {
|
||||
let validator = BasicAuthValidator::new(vec![], None);
|
||||
assert_eq!(validator.www_authenticate(), "Basic realm=\"Restricted\"");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user