kellnr_auth/
auth_req_token.rs1use axum::RequestPartsExt;
2use axum::body::Body;
3use axum::extract::{Request, State};
4use axum::http::HeaderValue;
5use axum::middleware::Next;
6use axum::response::Response;
7use tracing::warn;
8
9use crate::token::Token;
10
11pub async fn cargo_auth_when_required(
18 State(state): State<kellnr_appstate::AppStateData>,
19 request: Request,
20 next: Next,
21) -> Response {
22 if !state.settings.registry.auth_required {
28 return next.run(request).await;
30 }
31
32 let token = Token::from_header(request.headers(), &state).await;
33
34 match token {
35 Ok(_) => next.run(request).await,
36 Err(status) => {
37 warn!("Authentication required, but failed: {status}");
40 let mut response = Response::new(Body::empty());
41
42 (*response.status_mut()) = status;
43 response.headers_mut().insert(
44 "WWW-Authenticate",
45 HeaderValue::from_static("Basic, Bearer"),
46 );
47
48 response
49 }
50 }
51}
52
53pub async fn token_or_session_auth_when_required(
58 State(state): State<kellnr_appstate::AppStateData>,
59 request: Request,
60 next: Next,
61) -> Response {
62 if !state.settings.registry.auth_required {
63 return next.run(request).await;
64 }
65
66 if Token::from_header(request.headers(), &state).await.is_ok() {
68 return next.run(request).await;
69 }
70
71 let (mut parts, body) = request.into_parts();
75
76 let jar: axum_extra::extract::PrivateCookieJar = match parts.extract_with_state(&state).await {
77 Ok(j) => j,
78 Err(_) => return unauthorized_www_authenticate(),
79 };
80
81 let Some(cookie) = jar.get(kellnr_settings::constants::COOKIE_SESSION_ID) else {
82 return unauthorized_www_authenticate();
83 };
84
85 if state.db.validate_session(cookie.value()).await.is_ok() {
86 let request = Request::from_parts(parts, body);
87 return next.run(request).await;
88 }
89
90 unauthorized_www_authenticate()
91}
92
93fn unauthorized_www_authenticate() -> Response {
94 let mut response = Response::new(Body::empty());
97 *response.status_mut() = axum::http::StatusCode::UNAUTHORIZED;
98 response.headers_mut().insert(
99 "WWW-Authenticate",
100 HeaderValue::from_static("Basic, Bearer"),
101 );
102 response
103}
104
105#[cfg(test)]
106mod test {
107
108 use std::sync::Arc;
109
110 use axum::body::Body;
111 use axum::http::{Request, StatusCode, header};
112 use axum::routing::get;
113 use axum::{Router, middleware};
114 use kellnr_appstate::AppStateData;
115 use kellnr_db::User;
116 use kellnr_db::error::DbError;
117 use kellnr_db::mock::MockDb;
118 use kellnr_settings::Settings;
119 use mockall::predicate::*;
120 use tower::ServiceExt;
121
122 use super::*;
123
124 #[tokio::test]
125 async fn no_auth_required() {
126 let settings = test_settings(false);
127 let r = app(settings)
128 .oneshot(Request::get("/test").body(Body::empty()).unwrap())
129 .await
130 .unwrap();
131
132 assert_eq!(r.status(), StatusCode::OK);
133 }
134
135 #[tokio::test]
136 async fn auth_required_but_not_provided() {
137 let settings = test_settings(true);
138 let r = app(settings)
139 .oneshot(Request::get("/test").body(Body::empty()).unwrap())
140 .await
141 .unwrap();
142
143 assert_eq!(r.status(), StatusCode::UNAUTHORIZED);
144 }
145
146 #[tokio::test]
147 async fn auth_required_but_wrong_token_provided() {
148 let settings = test_settings(true);
149 let r = app(settings)
150 .oneshot(
151 Request::get("/test")
152 .header(header::AUTHORIZATION, "wrong_token")
153 .body(Body::empty())
154 .unwrap(),
155 )
156 .await
157 .unwrap();
158
159 assert_eq!(r.status(), StatusCode::FORBIDDEN);
160 }
161
162 #[tokio::test]
163 async fn auth_required_and_right_token_provided() {
164 let settings = test_settings(true);
165 let r = app(settings)
166 .oneshot(
167 Request::get("/test")
168 .header(header::AUTHORIZATION, "token")
169 .body(Body::empty())
170 .unwrap(),
171 )
172 .await
173 .unwrap();
174
175 assert_eq!(r.status(), StatusCode::OK);
176 }
177
178 pub async fn test_auth_req_token() -> StatusCode {
179 StatusCode::OK
180 }
181
182 fn test_settings(auth_required: bool) -> Settings {
183 Settings {
184 registry: kellnr_settings::Registry {
185 auth_required,
186 ..kellnr_settings::Registry::default()
187 },
188 ..Settings::default()
189 }
190 }
191
192 fn app(settings: Settings) -> Router {
193 let mut mock_db = MockDb::new();
194 mock_db
195 .expect_get_user_from_token()
196 .with(eq("token"))
197 .returning(move |_| {
198 Ok(User {
199 id: 0,
200 name: "user".to_string(),
201 pwd: String::new(),
202 salt: String::new(),
203 is_admin: false,
204 is_read_only: false,
205 created: String::new(),
206 })
207 });
208 mock_db
209 .expect_get_user_from_token()
210 .with(eq("wrong_token"))
211 .returning(move |_| Err(DbError::UserNotFound("user".to_string())));
212
213 let state = AppStateData {
214 db: Arc::new(mock_db),
215 settings: Arc::new(settings),
216 ..kellnr_appstate::test_state()
217 };
218
219 Router::new()
220 .route("/test", get(test_auth_req_token))
221 .route_layer(middleware::from_fn_with_state(
222 state.clone(),
223 cargo_auth_when_required,
224 ))
225 .with_state(state)
226 }
227}
228
229#[cfg(test)]
230mod auth_middleware_tests {
231 use std::sync::Arc;
232
233 use axum::Router;
234 use axum::body::Body;
235 use axum::http::StatusCode;
236 use axum::middleware::from_fn_with_state;
237 use axum::routing::get;
238 use hyper::{Request, header};
239 use kellnr_appstate::AppStateData;
240 use kellnr_db::DbProvider;
241 use kellnr_db::error::DbError;
242 use kellnr_db::mock::MockDb;
243 use kellnr_settings::Settings;
244 use mockall::predicate::*;
245 use tower::ServiceExt;
246
247 use super::*;
248
249 fn app_required_auth(db: Arc<dyn DbProvider>) -> Router {
250 let settings = Settings::default();
251 let state = AppStateData {
252 db,
253 settings: Arc::new(Settings {
254 registry: kellnr_settings::Registry {
255 auth_required: true,
256 ..kellnr_settings::Registry::default()
257 },
258 ..settings
259 }),
260 ..kellnr_appstate::test_state()
261 };
262
263 Router::new()
264 .route("/guarded", get(StatusCode::OK))
265 .route_layer(from_fn_with_state(state.clone(), cargo_auth_when_required))
266 .route("/not_guarded", get(StatusCode::OK))
267 .with_state(state)
268 }
269
270 fn app_not_required_auth(db: Arc<dyn DbProvider>) -> Router {
271 let settings = Settings::default();
272 let state = AppStateData {
273 db,
274 settings: Arc::new(settings),
275 ..kellnr_appstate::test_state()
276 };
277 Router::new()
278 .route("/guarded", get(StatusCode::OK))
279 .route_layer(from_fn_with_state(state.clone(), cargo_auth_when_required))
280 .with_state(state)
281 }
282
283 type Result<T = ()> = std::result::Result<T, Box<dyn std::error::Error>>;
284
285 #[tokio::test]
286 async fn guarded_route_with_invalid_token() -> Result {
287 let mut mock_db = MockDb::new();
288 mock_db
289 .expect_get_user_from_token()
290 .with(eq("1234"))
291 .returning(|_st| Err(DbError::UserNotFound("1234".to_owned())));
292
293 let r = app_required_auth(Arc::new(mock_db))
294 .oneshot(
295 Request::get("/guarded")
296 .header(header::AUTHORIZATION, "1234")
297 .body(Body::empty())?,
298 )
299 .await?;
300 assert_eq!(r.status(), StatusCode::FORBIDDEN);
301
302 Ok(())
303 }
304
305 #[tokio::test]
306 async fn guarded_route_without_token() -> Result {
307 let mock_db = MockDb::new();
308
309 let r = app_required_auth(Arc::new(mock_db))
310 .oneshot(Request::get("/guarded").body(Body::empty())?)
311 .await?;
312 assert_eq!(r.status(), StatusCode::UNAUTHORIZED);
313
314 Ok(())
315 }
316
317 #[tokio::test]
318 async fn not_guarded_route_without_token() -> Result {
319 let mock_db = MockDb::new();
320
321 let r = app_required_auth(Arc::new(mock_db))
322 .oneshot(Request::get("/not_guarded").body(Body::empty())?)
323 .await?;
324 assert_eq!(r.status(), StatusCode::OK);
325
326 Ok(())
327 }
328
329 #[tokio::test]
330 async fn app_not_required_auth_with_guarded_route() -> Result {
331 let mock_db = MockDb::new();
332
333 let r = app_not_required_auth(Arc::new(mock_db))
334 .oneshot(Request::get("/guarded").body(Body::empty())?)
335 .await?;
336 assert_eq!(r.status(), StatusCode::OK);
337
338 Ok(())
339 }
340}