reinhardt_admin/server/
cookie_auth.rs1use async_trait::async_trait;
21use reinhardt_auth::JwtAuth;
22use reinhardt_http::{
23 AuthState, Handler, IsActive, IsAdmin, IsAuthenticated, Middleware, Request, Response, Result,
24};
25use std::sync::Arc;
26
27use super::security::extract_admin_auth_cookie;
28
29pub struct AdminCookieAuthMiddleware {
46 jwt_auth: JwtAuth,
47}
48
49impl AdminCookieAuthMiddleware {
50 pub fn new(secret: &[u8]) -> Self {
52 Self {
53 jwt_auth: JwtAuth::new(secret),
54 }
55 }
56
57 pub fn from_jwt_auth(jwt_auth: JwtAuth) -> Self {
59 Self { jwt_auth }
60 }
61
62 fn extract_token(request: &Request) -> Option<String> {
64 if let Some(token) = extract_admin_auth_cookie(&request.headers) {
66 return Some(token);
67 }
68
69 request
71 .headers
72 .get("Authorization")
73 .and_then(|v| v.to_str().ok())
74 .and_then(|s| s.strip_prefix("Bearer "))
75 .map(|s| s.to_string())
76 }
77}
78
79#[async_trait]
80impl Middleware for AdminCookieAuthMiddleware {
81 async fn process(&self, request: Request, next: Arc<dyn Handler>) -> Result<Response> {
82 let auth_state = if let Some(token) = Self::extract_token(&request)
83 && let Ok(claims) = self.jwt_auth.verify_token(&token)
84 {
85 let user_id = claims.sub;
86 let is_admin = claims.is_staff || claims.is_superuser;
87 let is_active = true;
88
89 request.extensions.insert(user_id.clone());
92 request.extensions.insert(IsAuthenticated(true));
93 request.extensions.insert(IsAdmin(is_admin));
94 request.extensions.insert(IsActive(is_active));
95
96 AuthState::authenticated(user_id, is_admin, is_active)
97 } else {
98 AuthState::anonymous()
99 };
100
101 request.extensions.insert(auth_state);
102 next.handle(request).await
103 }
104}
105
106#[cfg(all(test, server))]
107mod tests {
108 use super::*;
109 use crate::server::security::ADMIN_AUTH_COOKIE_NAME;
110 use bytes::Bytes;
111 use hyper::{HeaderMap, Method, StatusCode, Version};
112 use reinhardt_auth::JwtAuth;
113
114 struct AuthCheckHandler;
115
116 #[async_trait]
117 impl Handler for AuthCheckHandler {
118 async fn handle(&self, request: Request) -> Result<Response> {
119 let auth_state: AuthState = request
120 .extensions
121 .get()
122 .unwrap_or_else(AuthState::anonymous);
123 let body = if auth_state.is_authenticated() {
124 format!("authenticated:{}", auth_state.user_id())
125 } else {
126 "anonymous".to_string()
127 };
128 Ok(Response::new(StatusCode::OK).with_body(body))
129 }
130 }
131
132 fn make_request(headers: HeaderMap) -> Request {
133 Request::builder()
134 .method(Method::POST)
135 .uri("/admin/api/server_fn/get_list")
136 .version(Version::HTTP_11)
137 .headers(headers)
138 .body(Bytes::new())
139 .build()
140 .unwrap()
141 }
142
143 fn test_secret() -> &'static [u8] {
144 b"test-secret-key-for-jwt-testing-purposes"
145 }
146
147 fn generate_test_token(user_id: &str) -> String {
148 let jwt_auth = JwtAuth::new(test_secret());
149 jwt_auth
150 .generate_token(user_id.to_string(), "admin".to_string(), true, false)
151 .unwrap()
152 }
153
154 #[tokio::test]
155 async fn test_no_token_returns_anonymous() {
156 let mw = AdminCookieAuthMiddleware::new(test_secret());
157 let next = Arc::new(AuthCheckHandler);
158 let req = make_request(HeaderMap::new());
159 let resp = mw.process(req, next).await.unwrap();
160 assert_eq!(resp.body, "anonymous");
161 }
162
163 #[tokio::test]
164 async fn test_cookie_token_authenticates() {
165 let token = generate_test_token("user-123");
166 let mw = AdminCookieAuthMiddleware::new(test_secret());
167 let next = Arc::new(AuthCheckHandler);
168 let mut headers = HeaderMap::new();
169 headers.insert(
170 "cookie",
171 format!("{}={}", ADMIN_AUTH_COOKIE_NAME, token)
172 .parse()
173 .unwrap(),
174 );
175 let req = make_request(headers);
176 let resp = mw.process(req, next).await.unwrap();
177 assert_eq!(resp.body, "authenticated:user-123");
178 }
179
180 #[tokio::test]
181 async fn test_bearer_token_authenticates_as_fallback() {
182 let token = generate_test_token("user-456");
183 let mw = AdminCookieAuthMiddleware::new(test_secret());
184 let next = Arc::new(AuthCheckHandler);
185 let mut headers = HeaderMap::new();
186 headers.insert(
187 "Authorization",
188 format!("Bearer {}", token).parse().unwrap(),
189 );
190 let req = make_request(headers);
191 let resp = mw.process(req, next).await.unwrap();
192 assert_eq!(resp.body, "authenticated:user-456");
193 }
194
195 #[tokio::test]
196 async fn test_cookie_takes_precedence_over_bearer() {
197 let cookie_token = generate_test_token("cookie-user");
198 let bearer_token = generate_test_token("bearer-user");
199 let mw = AdminCookieAuthMiddleware::new(test_secret());
200 let next = Arc::new(AuthCheckHandler);
201 let mut headers = HeaderMap::new();
202 headers.insert(
203 "cookie",
204 format!("{}={}", ADMIN_AUTH_COOKIE_NAME, cookie_token)
205 .parse()
206 .unwrap(),
207 );
208 headers.insert(
209 "Authorization",
210 format!("Bearer {}", bearer_token).parse().unwrap(),
211 );
212 let req = make_request(headers);
213 let resp = mw.process(req, next).await.unwrap();
214 assert_eq!(resp.body, "authenticated:cookie-user");
215 }
216
217 #[tokio::test]
218 async fn test_invalid_token_returns_anonymous() {
219 let mw = AdminCookieAuthMiddleware::new(test_secret());
220 let next = Arc::new(AuthCheckHandler);
221 let mut headers = HeaderMap::new();
222 headers.insert(
223 "cookie",
224 format!("{}=invalid.token.here", ADMIN_AUTH_COOKIE_NAME)
225 .parse()
226 .unwrap(),
227 );
228 let req = make_request(headers);
229 let resp = mw.process(req, next).await.unwrap();
230 assert_eq!(resp.body, "anonymous");
231 }
232}