auth_routes.rs (4365B)
1 use std::net::SocketAddr; 2 3 use axum::{ 4 Form, Json, 5 body::Body, 6 extract::{ConnectInfo, Extension, State}, 7 http::{HeaderMap, Request, StatusCode, header}, 8 middleware::Next, 9 response::{IntoResponse, Response}, 10 }; 11 12 use super::{ 13 AUTH_COOKIE_NAME, ActionResponse, AuthClientKey, AuthForm, AuthStatusResponse, 14 ChangePasswordForm, HttpState, action_json, 15 auth::session_token_hash, 16 request_auth::{ 17 auth_client_key, auth_cookie, auth_exempt_path, change_auth_password, csrf_required, 18 login_auth_password, request_is_authenticated, same_origin_request, setup_auth_password, 19 }, 20 }; 21 22 pub(super) async fn require_auth_middleware( 23 State(state): State<HttpState>, 24 headers: HeaderMap, 25 mut request: Request<Body>, 26 next: Next, 27 ) -> Response { 28 let path = request.uri().path().to_string(); 29 if csrf_required(request.method()) && !same_origin_request(&headers) { 30 return csrf_error().into_response(); 31 } 32 let client_key = auth_client_key( 33 &headers, 34 request 35 .extensions() 36 .get::<ConnectInfo<SocketAddr>>() 37 .map(|info| info.0), 38 ); 39 request.extensions_mut().insert(AuthClientKey(client_key)); 40 if auth_exempt_path(&path) { 41 return next.run(request).await; 42 } 43 let configured = state.ui_config.lock().await.auth_password_hash.is_some(); 44 if !configured { 45 return auth_error("authentication setup is required").into_response(); 46 } 47 if request_is_authenticated(&state, &headers).await { 48 return next.run(request).await; 49 } 50 auth_error("authentication required").into_response() 51 } 52 53 pub(super) async fn api_auth_status( 54 State(state): State<HttpState>, 55 headers: HeaderMap, 56 ) -> Json<AuthStatusResponse> { 57 let configured = state.ui_config.lock().await.auth_password_hash.is_some(); 58 let authenticated = configured && request_is_authenticated(&state, &headers).await; 59 Json(AuthStatusResponse { 60 configured, 61 authenticated, 62 }) 63 } 64 65 pub(super) async fn api_auth_setup_form( 66 State(state): State<HttpState>, 67 Extension(client_key): Extension<AuthClientKey>, 68 Form(form): Form<AuthForm>, 69 ) -> Response { 70 match setup_auth_password(&state, &form.password, &client_key.0).await { 71 Ok(cookie) => ([(header::SET_COOKIE, cookie)], action_json(Ok(()))).into_response(), 72 Err(error) => action_json(Err(error)).into_response(), 73 } 74 } 75 76 pub(super) async fn api_auth_login_form( 77 State(state): State<HttpState>, 78 Extension(client_key): Extension<AuthClientKey>, 79 Form(form): Form<AuthForm>, 80 ) -> Response { 81 match login_auth_password(&state, &form.password, &client_key.0).await { 82 Ok(cookie) => ([(header::SET_COOKIE, cookie)], action_json(Ok(()))).into_response(), 83 Err(error) => action_json(Err(error)).into_response(), 84 } 85 } 86 87 pub(super) async fn api_auth_logout_form( 88 State(state): State<HttpState>, 89 headers: HeaderMap, 90 ) -> Response { 91 if let Some(token) = auth_cookie(&headers) { 92 state 93 .auth_sessions 94 .lock() 95 .await 96 .remove(&session_token_hash(token)); 97 } 98 ( 99 [( 100 header::SET_COOKIE, 101 format!("{AUTH_COOKIE_NAME}=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0"), 102 )], 103 action_json(Ok(())), 104 ) 105 .into_response() 106 } 107 108 pub(super) async fn api_auth_change_password_form( 109 State(state): State<HttpState>, 110 Extension(client_key): Extension<AuthClientKey>, 111 Form(form): Form<ChangePasswordForm>, 112 ) -> Response { 113 match change_auth_password( 114 &state, 115 &form.old_password, 116 &form.new_password, 117 &client_key.0, 118 ) 119 .await 120 { 121 Ok(cookie) => ([(header::SET_COOKIE, cookie)], action_json(Ok(()))).into_response(), 122 Err(error) => action_json(Err(error)).into_response(), 123 } 124 } 125 126 fn auth_error(message: &str) -> (StatusCode, Json<ActionResponse>) { 127 ( 128 StatusCode::UNAUTHORIZED, 129 Json(ActionResponse { 130 ok: false, 131 error: Some(message.to_string()), 132 }), 133 ) 134 } 135 136 fn csrf_error() -> (StatusCode, Json<ActionResponse>) { 137 ( 138 StatusCode::FORBIDDEN, 139 Json(ActionResponse { 140 ok: false, 141 error: Some("same-origin request required".to_string()), 142 }), 143 ) 144 }