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 }