iuna

iuna - experimental devnet protocol
git clone https://iuna.jhx.app/git/iuna.git
Log | Files | Refs | README | LICENSE

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 }