#![allow(deprecated)]
use crate::{AuthenticationBackend, AuthenticationError, SimpleUser, User};
use reinhardt_http::Request;
use uuid::Uuid;
pub struct RemoteUserAuthentication {
header_name: String,
force_logout: bool,
}
impl RemoteUserAuthentication {
pub fn new() -> Self {
Self {
header_name: "REMOTE_USER".to_string(),
force_logout: true,
}
}
pub fn with_header(mut self, header: impl Into<String>) -> Self {
self.header_name = header.into();
self
}
pub fn force_logout(mut self, force: bool) -> Self {
self.force_logout = force;
self
}
}
impl Default for RemoteUserAuthentication {
fn default() -> Self {
Self::new()
}
}
#[async_trait::async_trait]
impl AuthenticationBackend for RemoteUserAuthentication {
async fn authenticate(
&self,
request: &Request,
) -> Result<Option<Box<dyn User>>, AuthenticationError> {
let header_value = request
.headers
.get(&self.header_name)
.and_then(|v| v.to_str().ok());
match header_value {
Some(username) if !username.is_empty() => {
Ok(Some(Box::new(SimpleUser {
id: Uuid::new_v5(&crate::USER_ID_NAMESPACE, username.as_bytes()),
username: username.to_string(),
email: String::new(),
is_active: true,
is_admin: false,
is_staff: false,
is_superuser: false,
})))
}
_ => {
Ok(None)
}
}
}
async fn get_user(&self, _user_id: &str) -> Result<Option<Box<dyn User>>, AuthenticationError> {
Ok(None)
}
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
use hyper::{HeaderMap, Method};
use rstest::rstest;
#[tokio::test]
async fn test_remote_user_with_header() {
let auth = RemoteUserAuthentication::new();
let mut headers = HeaderMap::new();
headers.insert("REMOTE_USER", "testuser".parse().unwrap());
let request = Request::builder()
.method(Method::GET)
.uri("/")
.headers(headers)
.body(Bytes::new())
.build()
.unwrap();
let result = auth.authenticate(&request).await.unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().get_username(), "testuser");
}
#[tokio::test]
async fn test_remote_user_without_header() {
let auth = RemoteUserAuthentication::new();
let request = Request::builder()
.method(Method::GET)
.uri("/")
.body(Bytes::new())
.build()
.unwrap();
let result = auth.authenticate(&request).await.unwrap();
assert!(result.is_none());
}
#[tokio::test]
async fn test_custom_header() {
let auth = RemoteUserAuthentication::new().with_header("X-Auth-User");
let mut headers = HeaderMap::new();
headers.insert("X-Auth-User", "alice".parse().unwrap());
let request = Request::builder()
.method(Method::GET)
.uri("/")
.headers(headers)
.body(Bytes::new())
.build()
.unwrap();
let result = auth.authenticate(&request).await.unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().get_username(), "alice");
}
#[rstest]
#[tokio::test]
async fn test_same_username_produces_same_id() {
let auth = RemoteUserAuthentication::new();
let mut headers1 = HeaderMap::new();
headers1.insert("REMOTE_USER", "testuser".parse().unwrap());
let request1 = Request::builder()
.method(Method::GET)
.uri("/")
.headers(headers1)
.body(Bytes::new())
.build()
.unwrap();
let mut headers2 = HeaderMap::new();
headers2.insert("REMOTE_USER", "testuser".parse().unwrap());
let request2 = Request::builder()
.method(Method::GET)
.uri("/")
.headers(headers2)
.body(Bytes::new())
.build()
.unwrap();
let user1 = auth.authenticate(&request1).await.unwrap().unwrap();
let user2 = auth.authenticate(&request2).await.unwrap().unwrap();
assert_eq!(
user1.id(),
user2.id(),
"same username must produce the same UUID"
);
}
#[rstest]
#[tokio::test]
async fn test_authenticated_user_has_default_privilege_flags() {
let auth = RemoteUserAuthentication::new();
let mut headers = HeaderMap::new();
headers.insert("REMOTE_USER", "testuser".parse().unwrap());
let request = Request::builder()
.method(Method::GET)
.uri("/")
.headers(headers)
.body(Bytes::new())
.build()
.unwrap();
let user = auth.authenticate(&request).await.unwrap().unwrap();
assert!(user.is_active());
assert!(!user.is_admin());
}
#[rstest]
#[tokio::test]
async fn test_get_user_always_returns_none() {
let auth = RemoteUserAuthentication::new();
let result = auth.get_user("any_user_id").await.unwrap();
assert!(result.is_none());
}
#[tokio::test]
async fn test_empty_header() {
let auth = RemoteUserAuthentication::new();
let mut headers = HeaderMap::new();
headers.insert("REMOTE_USER", "".parse().unwrap());
let request = Request::builder()
.method(Method::GET)
.uri("/")
.headers(headers)
.body(Bytes::new())
.build()
.unwrap();
let result = auth.authenticate(&request).await.unwrap();
assert!(result.is_none());
}
}