reinhardt_auth/
remote_user.rs1use crate::core::AuthIdentity;
7use crate::internal_user::InternalUser;
8use crate::{AuthBackend, AuthenticationError};
9use reinhardt_http::Request;
10use uuid::Uuid;
11
12pub struct RemoteUserAuthentication {
52 header_name: String,
54 force_logout: bool,
56}
57
58impl RemoteUserAuthentication {
59 pub fn new() -> Self {
69 Self {
70 header_name: "REMOTE_USER".to_string(),
71 force_logout: true,
72 }
73 }
74
75 pub fn with_header(mut self, header: impl Into<String>) -> Self {
86 self.header_name = header.into();
87 self
88 }
89
90 pub fn force_logout(mut self, force: bool) -> Self {
92 self.force_logout = force;
93 self
94 }
95}
96
97impl Default for RemoteUserAuthentication {
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103#[async_trait::async_trait]
104impl AuthBackend for RemoteUserAuthentication {
105 async fn authenticate(
106 &self,
107 request: &Request,
108 ) -> Result<Option<Box<dyn AuthIdentity>>, AuthenticationError> {
109 let header_value = request
111 .headers
112 .get(&self.header_name)
113 .and_then(|v| v.to_str().ok());
114
115 match header_value {
116 Some(username) if !username.is_empty() => {
117 Ok(Some(Box::new(InternalUser {
119 id: Uuid::new_v5(&crate::USER_ID_NAMESPACE, username.as_bytes()),
120 username: username.to_string(),
121 email: String::new(),
122 is_active: true,
123 is_admin: false,
124 is_staff: false,
125 is_superuser: false,
126 })))
127 }
128 _ => {
129 Ok(None)
131 }
132 }
133 }
134
135 async fn get_user(
136 &self,
137 _user_id: &str,
138 ) -> Result<Option<Box<dyn AuthIdentity>>, AuthenticationError> {
139 Ok(None)
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use bytes::Bytes;
149 use hyper::{HeaderMap, Method};
150 use rstest::rstest;
151
152 #[tokio::test]
153 async fn test_remote_user_with_header() {
154 let auth = RemoteUserAuthentication::new();
155 let mut headers = HeaderMap::new();
156 headers.insert("REMOTE_USER", "testuser".parse().unwrap());
157
158 let request = Request::builder()
159 .method(Method::GET)
160 .uri("/")
161 .headers(headers)
162 .body(Bytes::new())
163 .build()
164 .unwrap();
165
166 let result = auth.authenticate(&request).await.unwrap();
167 let user = result.expect("remote user authentication should succeed");
168 let expected_id = Uuid::new_v5(&crate::USER_ID_NAMESPACE, b"testuser").to_string();
169 assert_eq!(user.id(), expected_id);
170 assert!(user.is_authenticated());
171 }
172
173 #[tokio::test]
174 async fn test_remote_user_without_header() {
175 let auth = RemoteUserAuthentication::new();
176 let request = Request::builder()
177 .method(Method::GET)
178 .uri("/")
179 .body(Bytes::new())
180 .build()
181 .unwrap();
182
183 let result = auth.authenticate(&request).await.unwrap();
184 assert!(result.is_none());
185 }
186
187 #[tokio::test]
188 async fn test_custom_header() {
189 let auth = RemoteUserAuthentication::new().with_header("X-Auth-User");
190 let mut headers = HeaderMap::new();
191 headers.insert("X-Auth-User", "alice".parse().unwrap());
192
193 let request = Request::builder()
194 .method(Method::GET)
195 .uri("/")
196 .headers(headers)
197 .body(Bytes::new())
198 .build()
199 .unwrap();
200
201 let result = auth.authenticate(&request).await.unwrap();
202 let user = result.expect("custom header authentication should succeed");
203 let expected_id = Uuid::new_v5(&crate::USER_ID_NAMESPACE, b"alice").to_string();
204 assert_eq!(user.id(), expected_id);
205 assert!(user.is_authenticated());
206 }
207
208 #[rstest]
209 #[tokio::test]
210 async fn test_same_username_produces_same_id() {
211 let auth = RemoteUserAuthentication::new();
213
214 let mut headers1 = HeaderMap::new();
215 headers1.insert("REMOTE_USER", "testuser".parse().unwrap());
216 let request1 = Request::builder()
217 .method(Method::GET)
218 .uri("/")
219 .headers(headers1)
220 .body(Bytes::new())
221 .build()
222 .unwrap();
223
224 let mut headers2 = HeaderMap::new();
225 headers2.insert("REMOTE_USER", "testuser".parse().unwrap());
226 let request2 = Request::builder()
227 .method(Method::GET)
228 .uri("/")
229 .headers(headers2)
230 .body(Bytes::new())
231 .build()
232 .unwrap();
233
234 let user1 = auth.authenticate(&request1).await.unwrap().unwrap();
236 let user2 = auth.authenticate(&request2).await.unwrap().unwrap();
237
238 assert_eq!(
240 user1.id(),
241 user2.id(),
242 "same username must produce the same UUID"
243 );
244 }
245
246 #[rstest]
247 #[tokio::test]
248 async fn test_authenticated_user_has_default_privilege_flags() {
249 let auth = RemoteUserAuthentication::new();
251 let mut headers = HeaderMap::new();
252 headers.insert("REMOTE_USER", "testuser".parse().unwrap());
253 let request = Request::builder()
254 .method(Method::GET)
255 .uri("/")
256 .headers(headers)
257 .body(Bytes::new())
258 .build()
259 .unwrap();
260
261 let user = auth.authenticate(&request).await.unwrap().unwrap();
263
264 assert!(user.is_authenticated());
266 assert!(!user.is_admin());
267 }
268
269 #[rstest]
270 #[tokio::test]
271 async fn test_get_user_always_returns_none() {
272 let auth = RemoteUserAuthentication::new();
274
275 let result = auth.get_user("any_user_id").await.unwrap();
277
278 assert!(result.is_none());
280 }
281
282 #[tokio::test]
283 async fn test_empty_header() {
284 let auth = RemoteUserAuthentication::new();
285 let mut headers = HeaderMap::new();
286 headers.insert("REMOTE_USER", "".parse().unwrap());
287
288 let request = Request::builder()
289 .method(Method::GET)
290 .uri("/")
291 .headers(headers)
292 .body(Bytes::new())
293 .build()
294 .unwrap();
295
296 let result = auth.authenticate(&request).await.unwrap();
297 assert!(result.is_none());
298 }
299}