taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

auth.rs (3577B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2024-2025 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 
     17 use std::sync::Arc;
     18 
     19 use axum::{
     20     extract::{Request, State},
     21     http::{
     22         HeaderValue,
     23         header::{self, WWW_AUTHENTICATE},
     24     },
     25     middleware::Next,
     26     response::{IntoResponse, Response},
     27 };
     28 use taler_common::error_code::ErrorCode;
     29 
     30 use crate::error::{failure, failure_code};
     31 
     32 pub enum AuthMethod {
     33     Basic(String),
     34     Bearer(String),
     35     None,
     36 }
     37 
     38 pub struct AuthMiddlewareState {
     39     method: AuthMethod,
     40     challenge: HeaderValue,
     41 }
     42 
     43 impl AuthMiddlewareState {
     44     pub fn new(method: AuthMethod, realm: &str) -> Self {
     45         let challenge = match method {
     46             AuthMethod::Basic(_) => format!("Basic realm=\"{realm}\" charset=\"UTF-8\""),
     47             AuthMethod::Bearer(_) => format!("Bearer realm=\"{realm}\""),
     48             AuthMethod::None => String::new(),
     49         };
     50         Self {
     51             challenge: HeaderValue::from_str(&challenge).unwrap(),
     52             method,
     53         }
     54     }
     55 }
     56 
     57 pub async fn auth_middleware(
     58     State(state): State<Arc<AuthMiddlewareState>>,
     59     req: Request,
     60     next: Next,
     61 ) -> Response {
     62     fn parse_auth<'a>(
     63         req: &'a Request,
     64         scheme: &'static str,
     65         challenge: &HeaderValue,
     66     ) -> Result<&'a str, crate::error::ApiError> {
     67         let Some(authorisation) = req.headers().get(header::AUTHORIZATION) else {
     68             return Err(failure(
     69                 ErrorCode::GENERIC_UNAUTHORIZED,
     70                 "Authorization header not found",
     71             )
     72             .with_header(WWW_AUTHENTICATE, challenge.clone()));
     73         };
     74 
     75         let Some((hscheme, parameter)) = authorisation
     76             .to_str()
     77             .ok()
     78             .and_then(|it| it.split_once(' '))
     79         else {
     80             return Err(failure(
     81                 ErrorCode::GENERIC_UNAUTHORIZED,
     82                 "Authorization header is malformed",
     83             ));
     84         };
     85 
     86         if scheme != hscheme {
     87             return Err(failure(
     88                 ErrorCode::GENERIC_UNAUTHORIZED,
     89                 format!("Authorization method '{hscheme}' wrong or not supported"),
     90             ));
     91         }
     92 
     93         Ok(parameter)
     94     }
     95 
     96     match &state.method {
     97         AuthMethod::Basic(token) => match parse_auth(&req, "Basic", &state.challenge) {
     98             Ok(htoken) => {
     99                 if htoken != token {
    100                     return failure_code(ErrorCode::GENERIC_TOKEN_UNKNOWN).into_response();
    101                 }
    102             }
    103             Err(err) => return err.into_response(),
    104         },
    105         AuthMethod::Bearer(token) => match parse_auth(&req, "Bearer", &state.challenge) {
    106             Ok(htoken) => {
    107                 if htoken != token {
    108                     return failure_code(ErrorCode::GENERIC_TOKEN_UNKNOWN).into_response();
    109                 }
    110             }
    111             Err(err) => return err.into_response(),
    112         },
    113         AuthMethod::None => {}
    114     }
    115     next.run(req).await
    116 }